最近学习《
编程之美》第1.3节,最初自己看完题目在
Linux下使用
C语言进行了实现。其中题目要求输出最优化的方案(题目如下图),自己并没有想到很好的最优方案,因此只是按照自己的理解先进行了实现。
![](http://hi.csdn.net/attachment/201112/5/0_1323052783p63P.gif)
实现方法如下:
首先是建模,即将实际问题转化为程序实现方法。分析了翻饼过程类似于排序,只不过是特殊的排序,每次排序只能从开头元素到指定位置之间的元素进行逆序,因此
设定数组的开头代表一摞烙饼的最上端,每次对数组排序需从数组第一个元素开始。受排序冒泡实现启发,每次排序将最大的烙饼排到最底下,当最大的排在最底下(数组最末尾)时,可以对除最大之外的烙饼进行翻转,因此使用
递归实现。
下面是程序是实现(未看解答之前):
#
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;
}
}
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
# 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;
}
}
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}的结果。
![](http://hi.csdn.net/attachment/201112/5/0_1323052813zrGV.gif)
几点
总结:
1. 学习上面C++实现中函数Search的实现,
穷举法与递归法组合实现,递归在穷举实现的循环中!!!
2. 学习这种迭代式的
思考方法,一开始的方案可能并不最优,但是不断的查找问题并解决。
3. 上面的方案仍然存在可优化的地方。虽然上面的方法肯定可以找到最优方案,但是实现并不是最优的,比如上面的搜索次数可以降低。书中提到,可以通过改进上界和下界来提高搜素效率(这也正是程序中使用上界和下届的方法),如何改进呢?
关于上界的分析:如果对于烙饼每次都需要翻转两次才能将最大的烙饼放在最低端,那么n个烙饼最多需要2(n-1)次就可排序成功。将上面程序的实现中上界改为(nCakeCnt - 1) * 2,之后运行,结果如下:
![](http://hi.csdn.net/attachment/201112/5/0_13230528281yk6.gif)
很明显搜素次数下降了400多次!!!!可见修改上界和下届可以很好的降低搜素次数。
使用一个测试用例自己来分析一遍上面的
算法,发现有许多无用的翻转。据作者说有一些这方面的论文,查找了一下确实发现了一些,有空研究一下。