《编程之美》1.3一摞烙饼的排序

      本文内容主要整理自http://yangguosheng.here.blog.163.com/blog/static/111479292201503111482479/、
http://blog.csdn.net/tianshuai1111/article/details/7659673、
http://blog.csdn.net/jiyangsb/article/details/45970381以及
http://blog.csdn.net/kabini/article/details/2276723。代码摘抄自《编程之美》相应章节并做了一些修改。
      文中最关键的代码就一处,就是调用递归的地方。
//递归进行翻转
for (i = 1; i < m_nCakeCnt; i++) {Revert(0, i);m_ReverseCakeArraySwap[step] = i;Search(step + 1);Revert(0, i);}

我们先来理解这个Search()递归函数干了什么,参数只有一个,即第几步,或者第几次。我们常用的递归中,一般都是一条线向下纵深递归,直到找到答案,再层层返回。但是这里的递归是树形的,每向下纵深一层,都会出现多个向下分支(就像树上每个节点都有多个子节点),也就是上面for循环的作用,每一次for循环就是一个子节点。
      书中的思路是给我一个数组,把这个数组当成树的根节点(每个节点代表一个数组,一种顺序),找到这个数组翻转一次所有可能的结果,也就是反转前两个,前三个,前N个,一直到反转整个数组。把每一种结果都当做这个跟节点的子节点。然后再以同样的方法找到每个子节点的子节点。这样就形成一个无线向下拓展的树,每一条从根节点到末节点的路径,就是一个翻转顺序,翻转方案,根节点(最原始数组)调用search时step是0,第一层子节点step是1,第二层子节点是2,以此类推。下图很形象的说明了书中的思路。


      当然我们是不用一直这样向下拓展的,每向下拓展一层,说明就要翻转一次。我们知道一摞烙饼最多要翻转的次数(书中说是2(n-1),但是我认为应该是2(n-2)+1)。也就是最多拓展到2(n-2)+1层即可。我们还知道最少需要翻转的次数,书中的算法我觉得也有问题,如果烙饼的次序就是从上面往下数是从大到小的顺序,按他的计算方法,最小次数就是0.但是其实应该是1,需要整个翻转一次。也就是他只判断了两两关系,而没有判断顺序。当我们递归到第N层的时候,只要判断已经翻转的次数(step)加上当前数组(翻转step次后的顺序)还需要的最少的次数如果大于翻转原始数组的上界次数,那么我们就能知道,这条纵深,不是最佳翻转顺序。也就是书中第一种return的情况。
      当突然发现,某个节点的数组顺序已经翻转好了。那么这个节点的深度就是翻转的次数。这不一定是最优的,但是却有了比最大翻转次数小的翻转方案,所以后面可以以当前这个翻转次数为最大翻转次数,继续找比这更优的次数。就是第二个return。
      回过头来,我们再看看那个最不好理解,递归调用的代码。
//递归进行翻转
for (i = 1; i < m_nCakeCnt; i++) {Revert(0, i);m_ReverseCakeArraySwap[step] = i;Search(step + 1);Revert(0, i);}

上面已经说过,for循环是遍历所有的翻转方法,看看两个Reverse(o,i),其实就是翻转了,然后再翻转回来。因为在书中的递归中,翻转的数组始终只有一个m_ReverseCakeArray,如果每次递归都创建新的数组可能就好理解一点了。因为书中始终翻转一个数组,当退出一次递归的时候,必须将数组还原到之前的状态(顺序),继续下一次递归。。
      m_ReverseCakeArraySwap[step] = i; 记录翻转步骤。
      对书中代码中的一些修改或改进:

      [1]变量m_arrSwap应该是m_SwapArray,因为m_arrSwap从未被定义过;
      [2]upBound()方法中应改为return (nCakeCnt-1)*2,当然这个上界不改也行,只是我想这里应该和前面说的相对应。也可以优化为return (nCakeCnt - 2) * 2 + 1,当然这也不是最优解。。。
      [3]Search()方法的剪枝部分,if( step+nElimate > m_nMaxSwap)有误,因为nElimate可能为0,所以当step等于m_nMaxSwap时候 会造成下面的m_reverseCakeArraySwap[step] = i这个语句的数组越界。所以应该改为:
if( step + nEstimate >= m_nMaxSwap)。
      [4]判断下界时,如果最大的烙饼不在最后一个位置,则要多翻转一次,因而在LowerBound函数return ret; 前插入行:
 if (pCakeArray[nCakeCnt-1] != nCakeCnt-1)ret++;

下面是修改后的代码

#include<iostream>
#include<assert.h>
using namespace std;/****************/
//
//烙饼排序的实现
//
/****************/
class CPrefixSorting {
private:int* m_CakeArray;     //烙饼信息数组int m_nCakeCnt;       //烙饼个数int m_nMaxSwap;       //最多交换次数。根据前面的推断,这里最多为(m_nCakeCnt-2)*2+1int* m_SwapArray;     //交换结果数组int* m_ReverseCakeArray;      //当前翻转烙饼信息数组int* m_ReverseCakeArraySwap;  //当前翻转烙饼交换结果数组int m_nSearch;                //当前搜索次数信息public:CPrefixSorting(){   //构造函数m_nCakeCnt = 0;m_nMaxSwap = 0;}~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;}}////计算烙饼翻转信息//@param//pCakeArray  存储烙饼索引数组//nCakeCnt     烙饼个数//void Run(int* pCakeArray, int nCakeCnt) {Init(pCakeArray, nCakeCnt);m_nSearch = 0;Search(0);}////输出烙饼翻转次数//void Output() {for (int i = 0; i < m_nMaxSwap; i++) {cout << m_SwapArray[i] << " ";}cout << endl << " |Search Times| : " << m_nSearch << endl;cout << "Total Swap Times = " << m_nMaxSwap << endl;}private:////初始化数组信息//@param//pCakeArray  存储烙饼索引数组//nCakeCnt//void Init(int* pCakeArray, int nCakeCnt) {assert(pCakeArray != NULL);assert(nCakeCnt > 0);m_nCakeCnt = nCakeCnt;//初始化烙饼数组m_CakeArray = new int[m_nCakeCnt];assert(m_CakeArray != NULL);for (int i = 0; i < m_nCakeCnt; i++) {m_CakeArray[i] = pCakeArray[i];}//设置最多交换次数信息m_nMaxSwap = UpBound(m_nCakeCnt);//初始化交换结果数组m_SwapArray = new int[m_nMaxSwap + 1];assert(m_SwapArray!=NULL);//初始化中间交换结果信息m_ReverseCakeArray = new int[m_nCakeCnt];for (int i = 0; i < m_nCakeCnt; i++) {m_ReverseCakeArray[i] = m_CakeArray[i];}m_ReverseCakeArraySwap = new int[m_nMaxSwap];}////寻找当前翻转的上界//int UpBound(int nCakeCnt) {return (nCakeCnt - 2) * 2 + 1;//原先return (nCakeCnt-1)*2也可以,//代码这么写也没问题,只不过不是最优解而已}////寻找当前翻转的下界//int LowerBound(int* pCakeArray, int nCakeCnt) {int t, ret = 0;//根据当前数组排序信息情况判断至少需要交换多少次for (int i = 1; i < nCakeCnt; i++) {//判断位置相邻的两个烙饼,是否为尺寸排序上相邻的//此处应该考虑顺序问题,若烙饼的次序从上往下数是从大到小的,即t==-1//翻转次数应该是1而非0,即要整个翻转一次。t = pCakeArray[i] - pCakeArray[i - 1];if ((t == 1) || (t == -1)) {}else {ret++;}}//判断下界时,如果最大的烙饼不在最后一个位置,则要多翻转一次(包含了t==-1的情况)//能有效减少无效搜索次数,虽然还是会包含无效搜索。。if (pCakeArray[nCakeCnt - 1] != nCakeCnt - 1)ret++;return ret;}//排序的主函数void Search(int step) {int i, nEstimate;m_nSearch++;//估算这次搜索所需要的最小交换次数nEstimatenEstimate = LowerBound(m_ReverseCakeArray, m_nCakeCnt);//根节点(最原始数组)调用search时step是0,第一层子节点step是1,//第二层子节点step是2,以此类推可知step是从0开始计数的。因为nElimate可能为0,//所以当step等于m_nMaxSwap时候,会造成下面的m_reverseCakeArraySwap[step]=i;//的数组越界。所以判断条件应改为>=if (step + nEstimate >= m_nMaxSwap)return;//如果已经排序好,即翻转完成,输出结果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;Search(step + 1);Revert(0, i);}}////true:已经排好序//false:未排序//bool IsSorted(int* pCakeArray, int nCakeCnt) { //若数组内容从小到大有序,返回truefor (int i = 1; i < nCakeCnt; i++) {if (pCakeArray[i - 1] > pCakeArray[i]) {return false;}}return true;}////翻转烙饼信息//void Revert(int nBegin, int nEnd) { //把数组中给定两个系数之间的内容反序之~assert(nEnd > nBegin);int i, j, t;//翻转烙饼信息for (i = nBegin, j = nEnd; i < j; i++, j--) {t = m_ReverseCakeArray[i];m_ReverseCakeArray[i] = m_ReverseCakeArray[j];m_ReverseCakeArray[j] = t;}}
};//主函数,供测试
int main() {int Cake[10] = {3,2,1,6,5,4,9,8,7,0};CPrefixSorting TestA;TestA.Run(Cake, 10);TestA.Output();return 0;
}

运行结果如下图所示:


      感想:没有大的改变,理解原先代码已经不容易了,在成为菜鸡的路上越走越远了。。。。

《编程之美》1.3一摞烙饼的排序相关推荐

  1. 编程之美 1.3 一摞烙饼的排序

    一摞烙饼的排序     有大小不一的一摞饼在你的一只手上,现在你需要将饼由大到小翻转,即最大尺寸的饼在底部,最小尺寸的饼在最上面,而且你只能用一只手进行翻转.千言万语不如一张图.     上图是三张饼 ...

  2. 编程之美学习笔记--一摞烙饼的排序

    问题:假设有n块大小不一的烙饼,翻烙饼时只能从最上面的烙饼开始,一次抓住最上面的几块饼,把它们上下颠倒个儿,那么最少要翻多少次,才能够达到最后的大小有序? 思路 先上一张图,可以很好的说明思路: 假设 ...

  3. 1.3 一摞烙饼的排序

    1.3 一摞烙饼的排序 参考<编程之美–1.3 一摞烙饼的排序> 问题描述: 一摞乱序摆放的烙饼,每次只能抓取最上面几块烙饼并翻转,多次翻转后能够实现烙饼的从小到大(从上往下)的有序摆放. ...

  4. 《编程之美——微软技术面试心得》一摞烙饼的排序初体验

    <编程之美>读书笔记:1.3 一摞烙饼的排序 问题: 星期五的晚上,一帮同事在希格玛大厦附近的"硬盘酒吧"多喝了几杯.程序员多喝了几杯之后谈什么呢?自然是算法问题.有个 ...

  5. 《编程之美》读书笔记(三):烙饼问题与搜索树

    <编程之美>读书笔记三:烙饼问题与搜索树 薛笛 EMail:jxuedi#gmail.com 前面已经写了一些关于烙饼问题的简单分析,但因为那天太累有些意犹未尽,今天再充实一些内容那这个问 ...

  6. 【编程之美】一摞烙饼的排序

    一,问题:                     星期五的晚上,一帮同事在希格玛大厦附近的"硬盘酒吧"多喝了几杯.程序员多喝了几杯之后谈什么呢?自然是算法问题.有个同事说:&qu ...

  7. 编程之美学习笔记(三):一摞烙饼的排序

    问题描述 星期五的晚上,一帮同事在希格玛大厦附近的"硬盘酒吧"多喝了几杯,程序员多喝了几杯之后谈什么呢?自然是算法 问题.有个同事说: "我以前在餐厅打工,顾客经常点非常 ...

  8. (1.5.1.3)编程之美:一摞烙饼的排序

    问题:     星期五的晚上,一帮同事在希格玛大厦附近的"硬盘酒吧"多喝了几杯.程序员多喝了几杯之后谈什么呢?自然是算法问题.有个同事说:"我以前在餐馆打工,顾客经常点非 ...

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

    前两个星期就看编程之美的一摞烙饼排序问题,刚开始看其代码没看懂什么意思,后来看了人家的博客才知道是怎么回事了,自己写了一遍其代码做各种各样的测试,吓我一跳,一个剪枝操作竟然省了那么多的时间,想起上一道 ...

最新文章

  1. JavaScript核心语法学习部分(四)
  2. JavaScript window.document的属性、方法和事件小结
  3. Python map 函数 - Python零基础入门教程
  4. python构建字典实现英文大写字母与ascii编码的转换_Python:将复杂的字符串字典从Unicode转换为ASCII...
  5. 第二部分:开发简要指南-第六章 使用Fragments构建动态UI
  6. jquery对象与js对象的相互转换
  7. 内核里面writel(readl)是如何实现的
  8. 公司电脑和家里电脑如何实现自动文件同步?
  9. 模电试题_数电试题 综合测试
  10. 企业架构 | TOGAF内容框架
  11. ORACLE 树形遍历查询根节点、父节点、子节点
  12. foobar2000 for mac(经典音乐播放器)
  13. 计算机领域车牌识别,城市智慧停车的智能眼睛——车牌识别系统
  14. mac用u盘安装linux系统教程视频,教你如何用u盘重装mac系统教程
  15. ipa包上传itunes store失败
  16. 泰坦尼克号生还率预测分析
  17. redis:客户端管理
  18. CPU的计算机能力和AVX512指令集
  19. web前端开发基础班课程大纲
  20. USB射频功率计DIY——构建软件校准方案

热门文章

  1. extjs 方法执行顺序_(软件工程)非结构化程序变为结构化程序的三种方法
  2. php js 二级联动_PHP+ajax实现二级联动菜单功能示例
  3. 保存pdf文件的一种方法
  4. 'nmake' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
  5. php5.6 mysql被重置_php5.6连接mysql8出现错误解决方法
  6. python html解析对比_python htmlparse页面解析示例
  7. mysql查binlog删除时间_mysql的binlog日志删除查询
  8. Java不满足的依赖异常_java – 新的缺失/不满足的依赖项WildFly 9中的错误
  9. java 根据类名示例化类_Java收集器类– 18个示例
  10. android捆绑demo_Android捆绑通知