前两个星期就看编程之美的一摞烙饼排序问题,刚开始看其代码没看懂什么意思,后来看了人家的博客才知道是怎么回事了,自己写了一遍其代码做各种各样的测试,吓我一跳,一个剪枝操作竟然省了那么多的时间,想起上一道题的将帅问题,顿时让我领悟到这编程之美的书籍,题目不但有意思,其代码真的优雅和美,好了接下来看这个烙饼排序问题。
题目:
星期五的晚上,一帮同事在希格玛大厦附近的“硬盘酒吧”多喝了几杯。程序员多喝了几杯之后谈什么呢?自然是算法问题。有个同事说:“我以前在餐馆打工,顾客经常点非常多的烙饼。店里的饼大小不一,我习惯在到达顾客饭桌前,把一摞饼按照大小次序摆好——小的在上面,大的在下面。由于我一只手托着盘子,只好用另一只手,一次抓住最上面的几块饼,把它们上下颠倒个个儿,反复几次之后,这摞烙饼就排好序了。我后来想,这实际上是个有趣的排序问题:假设有n块大小不一的烙饼,那最少要翻几次,才能达到最后大小有序的结果呢?”
你能否写出一个程序,对于n块大小不一的烙饼,输出最优化的翻饼过程呢?
编程之美的代码:
class CprefixSorting
{
public:
//初始化
CprefixSorting()
{
mCakecnt=0;
mMaxSwap=0;
};
//释放数组内存空间
virtual ~CprefixSorting()
{
if(mCakeArray!=NULL)
delete mCakeArray;
if(mReverseCakeArraySwap!=NULL)
delete mReverseCakeArraySwap;
if(mSwapArray!=NULL)
delete mSwapArray;
if(mReverseCakeArray!=NULL)
delete mReverseCakeArray;
};
//最大的上界
int UpperBound(int mCakecnt)
{
//这里修正了编程之美的上界
return 2*(mCakecnt-1);
}
//最小的下界
int LowerBound(int *reverseCake,int cakeCnt )
{
int t;
int reverseCnt=0;//统计最少需要翻转的次数
for(int i=1;i<cakeCnt;i++)
{
//判断是否相邻
t=reverseCake[i]-reverseCake[i-1];
if(t==1||t==-1)
{
}
else
{
reverseCnt++;
}
}
return reverseCnt;
}
//判断烙饼是否排好序
bool IsSorted(int* reverseCakeArray,int cakeCnt )
{
for(int i=1;i<cakeCnt;i++)
{
if(reverseCakeArray[i-1]>reverseCakeArray[i])
{
return false;
}
}
return true;
}
//初始化信息
void Init(int * cakeArray,int cakeCnt)
{
assert(cakeArray!=NULL);
assert(cakeCnt>0);
mCakecnt=cakeCnt;
mCakeArray=new int[mCakecnt];
assert(mCakeArray!=NULL);
for(int i=0;i<mCakecnt;i++)
{
mCakeArray[i]=cakeArray[i];
}
//设置最大存储空间
mMaxSwap=UpperBound(mCakecnt);
mSwapArray=new int[mMaxSwap+1];
assert(mSwapArray!=NULL);
mReverseCakeArray=new int[mCakecnt];
for(int i=0;i<mCakecnt;i++)
{
mReverseCakeArray[i]=mCakeArray[i];
}
mReverseCakeArraySwap=new int[mMaxSwap];
}
//排序的核心函数
void Search(int step)
{
//存储最小搜索次数
int lowCnt;
mSearch++;
lowCnt=LowerBound(mReverseCakeArray,mCakecnt);
//这里也修正了编程之美的剪枝
//当当前的搜索和最小的下界若比前面最小次数的还要大
//就停止搜索,以便减少时间的开销
//这剪枝是递归分支限界法的核心
if(step+lowCnt>mMaxSwap||step>=mMaxSwap)
{
return;
}
if(IsSorted(mReverseCakeArray,mCakecnt))
{
if(step<mMaxSwap)
{
mMaxSwap=step;
for(int i=0;i<mCakecnt;i++)
{
mSwapArray[i]=mReverseCakeArraySwap[i];
}
}
return;
}
//遍历递归进行翻转
for(int i=1;i<mCakecnt;i++)
{
//翻转
Reserver(0,i);
//储存当前搜索值对应翻转月饼的索引
mReverseCakeArraySwap[step]=i;
//搜索下一个节点
Search(step+1);
// printf("step:%d\n",step+1);
// for(int j=0;j<mCakecnt;j++)
// {
// printf("%d ",mReverseCakeArray[j]);
// }
// printf("\n");
//返回上一层
Reserver(0,i);
}
}
//翻转烙饼信息
void Reserver(int begin,int end)
{
for(int i=begin,j=end;i<j;i++,j--)
{
int t=mReverseCakeArray[i];
mReverseCakeArray[i]=mReverseCakeArray[j];
mReverseCakeArray[j]=t;
}
}
//运行函数接口
void Run(int *cakeArray,int cakeCnt)
{
Init(cakeArray,cakeCnt);
mSearch=0;
Search(0);
}
void Output()
{
for(int i=0;i<mMaxSwap;i++)
{
//输出每层翻转其烙饼的索引
printf("%d ",mSwapArray[i]);
}
//输出搜索次数
printf("\n |Search Times : %d\n",mSearch);
//输出需翻转最少的次数
printf("Total Swap times= %d\n",mMaxSwap);
}
private:
int * mCakeArray;//储存对应索引烙饼大小信息数组
int mCakecnt;//烙饼的个数
int mMaxSwap;//烙饼的最大交换次数
int * mReverseCakeArray;//储存当前翻转后对应索引烙饼的大小信息
int mSearch; //当前搜索次数
int * mReverseCakeArraySwap;//储存当前对应搜索值的被翻转的烙饼索引
int * mSwapArray;//存储已经完全排好序的所有对应搜索值得被烙饼索引
};
#endif // CPREFIXSORTING_H
int main()
{
CprefixSorting test;
int aa[3]={3,1,2};
test.Run(aa,3);
test.Output();
}
运行结果:
2 1
|Search Times : 19
Total Swap times= 2
剪枝操作的关键就是看其上界和下界两个边界,如果下界越大,上界越小其运行效率越高,测试:
修改上界值较大(调整更大的上界):
//最大的上界
int UpperBound(int mCakecnt)
{
return 2*mCakecnt;
}
运行结果
2 1
|Search Times : 31
修改下界值较小(调整更小的下界)
lowCnt=-1;
运行结果:
2 1
|Search Times : 31
Total Swap times= 2
从结果可以看出,上界和下界的数值对其所搜次数的影响,这里如果你传入的数组数目越多,影响会更明显的,因为这里涉及了一种搜索树的结构,该树深度越高,树的节点就越多,越密集那么消耗的时间的次数就越高,这时利用分支限界法(即巧妙的利用边界才停止其往下搜索),控制搜索深度,这样就可以大大的减少时间效率了,如图:
参考博客:
http://blog.csdn.net/zuiaituantuan/article/details/6056601