一摞烙饼的排序(搜索树)

前两个星期就看编程之美的一摞烙饼排序问题,刚开始看其代码没看懂什么意思,后来看了人家的博客才知道是怎么回事了,自己写了一遍其代码做各种各样的测试,吓我一跳,一个剪枝操作竟然省了那么多的时间,想起上一道题的将帅问题,顿时让我领悟到这编程之美的书籍,题目不但有意思,其代码真的优雅和美,好了接下来看这个烙饼排序问题。
题目:
星期五的晚上,一帮同事在希格玛大厦附近的“硬盘酒吧”多喝了几杯。程序员多喝了几杯之后谈什么呢?自然是算法问题。有个同事说:“我以前在餐馆打工,顾客经常点非常多的烙饼。店里的饼大小不一,我习惯在到达顾客饭桌前,把一摞饼按照大小次序摆好——小的在上面,大的在下面。由于我一只手托着盘子,只好用另一只手,一次抓住最上面的几块饼,把它们上下颠倒个个儿,反复几次之后,这摞烙饼就排好序了。我后来想,这实际上是个有趣的排序问题:假设有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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值