《编程之美》笔记-烙饼排序的思考

最近学习《
编程之美》第1.3节,最初自己看完题目在
Linux下使用
C语言进行了实现。其中题目要求输出最优化的方案(题目如下图),自己并没有想到很好的最优方案,因此只是按照自己的理解先进行了实现。
《《编程之美》笔记-烙饼排序的思考》

实现方法如下:   首先是建模,即将实际问题转化为程序实现方法。分析了翻饼过程类似于排序,只不过是特殊的排序,每次排序只能从开头元素到指定位置之间的元素进行逆序,因此
设定数组的开头代表一摞烙饼的最上端,每次对数组排序需从数组第一个元素开始。受排序冒泡实现启发,每次排序将最大的烙饼排到最底下,当最大的排在最底下(数组最末尾)时,可以对除最大之外的烙饼进行翻转,因此使用
递归实现。   下面是程序是实现(未看解答之前):
#
include
<stdio.h
> 

void cakeSort(
int cake[],
int size);
//排序函数,并输出排序方法

void reverse(
int array[],
int size);
//数组逆序

int biggestFind(
int array[],
int size);
//查找数组最大元素并输出位置

int step
=
0;

int main(
void)

{

    
int cake[]
= {
2,
4,
1,
5,
3};

    
int size
=
sizeof(cake)
/
sizeof(
int);

    cakeSort(cake, size);

    printf(
“共需要%d步翻转\n”, step);

    
int i;

    
for(i
=
0; i
< size; i
++) {

        printf(
“%d”, cake[i]);

    }

    printf(
“\n”);

    
return
0;

}

void cakeSort(
int cake[],
int size)

{

    
int maxLocation;

    
if(size
>
1) {

        maxLocation
= biggestFind(cake, size);
//获取最大值位置

        
if(maxLocation
==
0) {
//最大值在开头,则reverse整个数组

            reverse(cake, size);

            step
++;

            printf(
“第%d步:翻转上面的%d个饼\n”, step, size);

            cakeSort(cake, size

1);

        }
else
if(maxLocation
== size

1) {
//最大值在末尾,则对前面的数字进行排序

            cakeSort(cake, size

1);

        }
else {
//最大值在中间某位置,则将开始到最大值位置处的子数组进行reverse,使最大值放在开头处

            reverse(cake, maxLocation
+
1);

            step
++;

            printf(
“第%d步:翻转上面的%d个饼\n”, step, maxLocation
+
1);

            reverse(cake, size);

            step
++;

            printf(
“第%d步:翻转上面的%d个饼\n”, step, size);

            cakeSort(cake, size

1);

        }

    }

}

//功能:返回数组中最大值所在的位置 

#
include
<stdio.h
>

int biggestFind(
int array[],
int size)

{

    
int location, max;
//存储最大值所在位置和最大值

    
int i;

    
if(size
<
=
0)

    {

        printf(
“Null array!\n”);

        
return

1;

    }

    max
= array[
0];

    
for(i
=
0, location
=
0; i
< size; i
++)

    {

        
if(array[i]
> max)

        {

            max
= array[i];

            location
= i;

        }

    }

    
return location;

}

//将数组逆置 

void reverse(
int array[],
int size)

{

    
int i, temp;

    
for(i
=
0; i
< size
/
2; i
++) { 

        temp
= array[size

1
i];

        array[size

1
i]
= array[i];

        array[i]
= temp;

    }

}   在Linux下使用gcc编译通过,测试了几个测试用例都没没有发现错误,至少实现了自己最初的设想。然后怀着些许满意的心情看了书中的解答,发现自己想的太简单了。然后又使用了一个测试用例:数组{2, 4, 1, 5, 3},发现确实是有问题的,如果使用自己的方法需要翻转7次才能达到结果,而如果先将2和4翻转一下,再按照上面的方法却只需要5次就可以。而且看完书后的感觉是自己根本就没按照题目要求来解答,
题目要求的是输出最优方案,也就程序需要解决的问题是查找最优的方案,而不是输出排序后的结果以及排序方案。   看完书中的解答,仍然不是太明白具体的解法,分析书中的程序,发现该实现使用了穷举法,穷举了所有的翻转方法,然后输出最优的方案,其中每次翻转仍然采用的递归方案。书中的程序存在一些小的问题,下面是我修正后侧程序实现,只修改了一点点。 C++程序实现:修改之处:1.将声明和实现分开;2.修正了程序中的一个印刷bug;3.修改了程序输出 CPrefixSorting.h
#
ifndef CPREFIXSORTING_H 

#
define CPREFIXSORTING_H

#
include
<assert.h
>

#
include
<stdio.h
>

class CPrefixSorting

{

    
public
:

        CPrefixSorting();

        
~CPrefixSorting();

        
void Run(
int
* pCakeArray,
int nCakeCnt);
//计算最优翻转方法

        
void Output();
//输出最优翻转实现

    
protected
:

    
private
:

        
void Init(
int
* pCakeArray,
int nCakeCnt);
//初始化成员变量,实现了平常我们在构造函数中实现的功能

        
int UpBound(
int nCakeCnt);
//计算上界

        
int LowerBound(
int
* pCakeArray,
int nCakeCnt);
//计算下界

        
void Search(
int step);
//排序的主函数,递归实现,查找出最优翻转方法

        
bool IsSorted(
int
* pCakeArray,
int nCakeCnt);
//判断是否排序完成

        
void Revert(
int nBegin,
int nEnd);
//逆序一个数组

    
private
:

        
int
* m_CakeArray;
//给每一个大小不一的烙饼设定一个数值,数值的大小代表烙饼的大小,相邻大小烙饼数值相差1,该数组存放烙饼的数值

        
int m_nCakeCnt;
//烙饼个数

        
int m_nMaxSwap;
//最大翻转次数,最初设为上界,即烙饼个数的2倍

        
int
* m_SwapArray;
//存放翻转结果,数组的每一个数值代表翻转的上面的多少个烙饼

        
int
* m_ReverseCakeArray;
//表示当前烙饼信息数组

        
int
* m_ReverseCakeArraySwap;
//当前翻转烙饼交换信息数组

        
int m_nSearch;
// 表示进行了收索的次数

};

#
endif
// CPREFIXSORTING_H   CPrefixSorting.cpp  其中最重要的实现就是函数Search的实现
#
include
“/home/linux/BeautyOfProgramming/C1_3CakeSort_C++Version/CakeSort/include/CPrefixSorting.h” 

CPrefixSorting
:
:CPrefixSorting()

{

    m_nCakeCnt
=
0;

    m_nMaxSwap
=
0;

}

CPrefixSorting
:
:
~CPrefixSorting()

{

    
if(m_CakeArray
!= NULL)

    {

        
delete m_CakeArray;

    }

    
if(m_SwapArray
!= NULL)

    {

        
delete m_SwapArray;

    }

    
if(m_ReverseCakeArray
!= NULL)

    {

        
delete m_ReverseCakeArray;

    }

    
if(m_ReverseCakeArraySwap
!= NULL)

    {

        
delete m_ReverseCakeArraySwap;

    }

}

void CPrefixSorting
:
:Run(
int
* pCakeArray,
int nCakeCnt)

{

    Init(pCakeArray, nCakeCnt);

    m_nSearch
=
0;

    Search(
0);

}

void CPrefixSorting
:
:Output()

{

    
for(
int i
=
0; i
< m_nMaxSwap; i
++)

    {

        printf(
“%d step: revert %d Cakes above\n”, i, m_SwapArray[i]);

    }

    printf(
“\n |Search Times| : %d\n”, m_nSearch);

    printf(
“Total Swap times = %d\n”, m_nMaxSwap);

}

void CPrefixSorting
:
:Init(
int
* pCakeArray,
int nCakeCnt)

{

    assert(pCakeArray
!= NULL);

    assert(nCakeCnt
>
0);

    m_nCakeCnt
= nCakeCnt;

    
//init cake array

    m_CakeArray
=
new
int[m_nCakeCnt];

    assert(m_CakeArray
!= NULL);

    
for(
int i
=
0; i
< m_nCakeCnt; i
++) {

        m_CakeArray[i]
= pCakeArray[i];

    }

    
//set max swap info

    m_nMaxSwap
= UpBound(m_nCakeCnt);

    
//init swap result array

    m_SwapArray
=
new
int[m_nMaxSwap
+
1];

    assert(m_SwapArray
!= NULL);

    
//init swap result info

    m_ReverseCakeArray
=
new
int[m_nCakeCnt];

    assert(m_ReverseCakeArray);

    
for(
int i
=
0; i
< m_nCakeCnt; i
++) {

        m_ReverseCakeArray[i]
= m_CakeArray[i];

    }

    m_ReverseCakeArraySwap
=
new
int[m_nMaxSwap];

    assert(m_ReverseCakeArraySwap);

}

//find upbound

int CPrefixSorting
:
:UpBound(
int nCakeCnt)

{

    
return nCakeCnt
*
2;

}

//find downbound

int CPrefixSorting
:
:LowerBound(
int
* pCakeArray,
int nCakeCnt)

{

    
int t, ret
=
0;

    
//get the min number of swap, according the current sort info of array

    
for(
int i
=
1; i
< nCakeCnt; i
++)

    {

        
//判断位置相邻的两个酪饼,是否为尺寸排序上相邻的

        t
= pCakeArray[i]
pCakeArray[i

1];

        
if((t
==
1)
|| (t
==

1))

        {

        }

        
else

        {

            ret
++;

        }

    }

    
return ret;

}

void CPrefixSorting
:
:Search(
int step)

{

    
int i, nEstimate;

    m_nSearch
++;

    
//estimate the min search of swap

    nEstimate
= LowerBound(m_ReverseCakeArray, m_nCakeCnt);

    
if(step
+ nEstimate
> m_nMaxSwap)

    {

        
return;

    }

    
//if sorting finished, output

    
if(IsSorted(m_ReverseCakeArray, m_nCakeCnt))

    {

        
if(step
< m_nMaxSwap)

        {

            m_nMaxSwap
= step;

            
for(i
=
0; i
< m_nMaxSwap; i
++)

            {

                m_SwapArray[i]
= m_ReverseCakeArraySwap[i];

            }

        }

        
return;

    }

    
for(i
=
1; i
< m_nCakeCnt; i
++)

    {

        Revert(
0, i);

        m_ReverseCakeArraySwap[step]
= i
+
1;

        Search(step
+
1);

        Revert(
0, i);
//将翻转过的数组重置

    }

}

bool CPrefixSorting
:
:IsSorted(
int
* pCakeArray,
int nCakeCnt)

{

    
for(
int i
=
1; i
< nCakeCnt; i
++)

    {

        
if(pCakeArray[i

1]
> pCakeArray[i])

        {

            
return
false;

        }

    }

    
return
true;

}

void CPrefixSorting
:
:Revert(
int nBegin,
int nEnd)

{

    assert(nEnd
> nBegin);

    
int i, j, t;

    
//revert info

    
for(i
= nBegin, j
= nEnd; i
< j; i
++, j
)

    {

        t
= m_ReverseCakeArray[i];

        m_ReverseCakeArray[i]
= m_ReverseCakeArray[j];

        m_ReverseCakeArray[j]
= t;

    }

}   对上面的实现在linux下使用Code:blocks编译通过,并能输出正确结果。如下图所示:图中显示为使用测试用例:数组{2, 4, 1, 5, 3}的结果。
《《编程之美》笔记-烙饼排序的思考》

几点
总结: 1. 学习上面C++实现中函数Search的实现,
穷举法与递归法组合实现,递归在穷举实现的循环中!!! 2. 学习这种迭代式的
思考方法,一开始的方案可能并不最优,但是不断的查找问题并解决。 3. 上面的方案仍然存在可优化的地方。虽然上面的方法肯定可以找到最优方案,但是实现并不是最优的,比如上面的搜索次数可以降低。书中提到,可以通过改进上界和下界来提高搜素效率(这也正是程序中使用上界和下届的方法),如何改进呢?  
关于上界的分析:如果对于烙饼每次都需要翻转两次才能将最大的烙饼放在最低端,那么n个烙饼最多需要2(n-1)次就可排序成功。将上面程序的实现中上界改为(nCakeCnt – 1) * 2,之后运行,结果如下:
《《编程之美》笔记-烙饼排序的思考》

很明显搜素次数下降了400多次!!!!可见修改上界和下届可以很好的降低搜素次数。   使用一个测试用例自己来分析一遍上面的
算法,发现有许多无用的翻转。据作者说有一些这方面的论文,查找了一下确实发现了一些,有空研究一下。

    原文作者:zhugezhuzhu
    原文地址: https://blog.csdn.net/zhugezhuzhu/article/details/7040968
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞