问题描述

星期五的晚上,一帮同事在希格玛大厦附近的“硬盘酒吧”多喝了几杯,程序员多喝了几杯之后谈什么呢?自然是算法

问题。有个同事说:

“我以前在餐厅打工,顾客经常点非常多的烙饼。店里的烙饼大小不一,我习惯在到达顾客饭桌前,把一摞饼按照大小

次序摆好---小的在上面,大的在下面。由于我一只手托着盘子,只好用另一只手,一次抓住最上面的几块饼,把它们

上下颠倒个个儿,反复几次之后,这摞烙饼就排好序了。我后来想,这实际上是个有趣的排序问题:假设有n块大小

不一的摞饼,那最少要翻几次,才能达到大小有序的结果呢?”

从这段描述中我们可以很容易的就把问题抽象出来:给你一个由 n 个连续整数组成的数组,数组是无序的,现在要你

对这个数组进行升序排序,但只能对数组进行一种操作,就是只能对从数组第一个元素开始到数组中任意一个元素之

间的所有元素进行翻转。只是这样的话,问题还不是很麻烦。这里还要求我们写程序输出最优的翻转过程。

思路:

一般遇到最优解问题我们首先想到的就是动态规划、贪心以及分支界限三种方法。书中提到了动态规划和分支界限这

两种方法,但只给出了分支界限方法的思路以及代码,所以我就以书上的方法来讲讲自己的理解吧。

书上的代码用的是递归遍历+剪枝。既然是递归,就得有个退出的条件,不然就死循环了。在这个问题中,第一个递

归退出的条件理所当然就是所有的烙饼都已经排好序了。如果只是这样的,就不涉及到剪枝了。我们知道递归的特点

是简洁,但是其效率相对来说是比较低的,如果遇到很大的问题,在现实中就不适用了,而提高递归效率的唯一方法

就是剪枝,所以我们必须想办法找到剪枝的策略。

一般来说我们都是通过去除一些不必要的遍历来进行剪枝的,分析这个问题,我们不难发现,最多要翻转的次数不会

超过2*(n-1)次,至于这个数字是怎么来的,书上已经写的很明白了,这里就不多作累赘啦。如果只是等某种翻转次数

超过2*(n-1)再来放弃这种遍历的话,显然意义不大。按照书上给出的思路,以2*(n-1)作为上界(UpperBound),表

示最差方案;而下界(LowerBound)则是动态的,下界的取值是当前排序状态下,我们至少还要翻转的次数,程序

中我们用变量 nEstimate  表示。当当前翻转次数+nEstimate>UpperBound()时,说明继续遍历下去最后的结果会超过

上界,这时我们直接对其进行剪枝。

代码:

#include<cassert>
#include<iostream>
using namespace std;class PrefixSorting
{int* m_CakeArray;   //烙饼信息数组int m_nCakeCnt;     //烙饼个数int m_nMaxSwap;     //最多翻转次数,即上界int* m_SwapArray;   //交换结果数组,即保存最优解的每一步交换int* m_ReverseCakeArray;       //当前翻转烙饼信息数组int* m_ReverseCakeArraySwap;   //当前翻转烙饼交换结果数组int m_nSearch;                 //当前遍历次数信息
public:PrefixSorting(){m_nCakeCnt = 0;m_nMaxSwap = 0;}~PrefixSorting(){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 Run(int* pCakeArray, int nCakeCnt){Init (pCakeArray, nCakeCnt);m_nSearch = 0;Search (0);}/*输出烙饼翻转的具体信息*/void Output (){for (int i = 0; i < m_nMaxSwap; i++)printf("%d\n", m_SwapArray[i]);printf("Search Times : %d\n", m_nSearch);printf("Total Swap times = %d\n", m_nMaxSwap);}private: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 = UpperBound (m_nCakeCnt);m_SwapArray = new int[m_nMaxSwap];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 UpperBound(int nCakeCnt){return (nCakeCnt - 1) * 2; //书上这里是 nCakeCnt * 2}/*计算下界*/int LowerBound(int* pCakeArray, int nCakeCnt){int t;int ret = 0;for (int i = 1; i < nCakeCnt; i++){t = pCakeArray[i] - pCakeArray[i - 1];if ((t == 1) || (t == -1)){}else{ret++;}}return ret;}/*排序的主函数*/void Search(int step){int i, nEstimate;m_nSearch++;/*剪枝*/nEstimate = LowerBound (m_ReverseCakeArray, m_nCakeCnt);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 (int i = 1; i < m_nCakeCnt; i++){Reverse (0, i);m_ReverseCakeArraySwap[step] = i;Search (step + 1);Reverse (0, i);}}/*判断是否已经排好序*/bool IsSorted(int* pCakeArray, int nCakeCnt){for (int i = 1; i < nCakeCnt; i++){if (pCakeArray[i - 1] > pCakeArray[i])return false;}return true;}/*翻转烙饼*/void Reverse(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 a[] = {3, 2, 1, 6, 5, 4, 9, 8, 7, 0};PrefixSorting test;test.Run(a, 10);test.Output();system("pause");return 0;
}

编程之美学习笔记(三):一摞烙饼的排序相关推荐

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

    <编程之美>1.3一摞烙饼的排序       本文内容主要整理自http://yangguosheng.here.blog.163.com/blog/static/111479292201 ...

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

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

  3. 软件工程之美学习笔记三十八 37 | 遇到线上故障,你和高手的差距在哪里?

    <软件工作之美>材料地址:https://time.geekbang.org/column/article/97219 遇到线上故障,新手和高手的差距在哪里? 新手遇到复杂的线上故障,不知 ...

  4. Python学习笔记三之编程练习:循环、迭代器与函数

    Python学习笔记三之编程练习 1. 编程第一步 # 求解斐波纳契数列 #/user/bin/python3#Fibonacci series:斐波那契数列 #两个元素的总和确定了下一个数 a,b= ...

  5. 《Java并发编程实践》学习笔记之一:基础知识

    <Java并发编程实践>学习笔记之一:基础知识 1.程序与进程 1.1 程序与进程的概念 (1)程序:一组有序的静态指令,是一种静态概念:  (2)进程:是一种活动,它是由一个动作序列组成 ...

  6. Mr.J-- jQuery学习笔记(三十二)--jQuery属性操作源码封装

    扫码看专栏 jQuery的优点 jquery是JavaScript库,能够极大地简化JavaScript编程,能够更方便的处理DOM操作和进行Ajax交互 1.轻量级 JQuery非常轻巧 2.强大的 ...

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

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

  8. java 编程思想 多线程学习笔记

    java 编程思想 多线程学习笔记 一.如何创建多线程? 1.继承 java.lang.Thread 类 2.实现 java.lang.Runnable 接口 3.Callable接口 总之,在任何线 ...

  9. K8S 学习笔记三 核心技术 Helm nfs prometheus grafana 高可用集群部署 容器部署流程

    K8S 学习笔记三 核心技术 2.13 Helm 2.13.1 Helm 引入 2.13.2 使用 Helm 可以解决哪些问题 2.13.3 Helm 概述 2.13.4 Helm 的 3 个重要概念 ...

最新文章

  1. JAVA问题定位跟踪技术
  2. 在 Virt-manager 图形界面中使用桥接网络
  3. 挑战蓝牙/Wi-Fi NFA争食无线音频应用大饼(转)
  4. ElasticSearch小操之Marvel,Sense
  5. Python 入门篇-用Notepad++编写出第一个python程序
  6. Maven 发布到本地仓库
  7. php无法完成文件上传,php – Fine Uploader无法上传文件
  8. Angular HttpClient.get的实现单步调试
  9. java判断输入月份_Java输入年份和月份判断多少天实例代码
  10. dw1000 github_GitHub打破了我的1000天连胜纪录
  11. windows系统通过git上传代码
  12. Android亮度调节
  13. android错误之解析包时出现问题(一)
  14. LVGL8制作简易时钟
  15. python-函数读取内置函数序列化与反序列化
  16. [VCS]后仿真中的几个基本概念
  17. 1838.最高频元素的频数
  18. UAP Resource
  19. 综合评价方法(一)------基础知识
  20. 2022年版中国人工智能芯片行业专项调研及投资前景方向预测报告

热门文章

  1. 机器人HEXA登陆DEFCon黑客大会,上演拆弹竞赛
  2. 历代华为P系列主要参数对比,更新于2021年7月
  3. 计算机应用行距怎么弄,电脑行间距在哪里设置
  4. 求生之路2联机服务器没有响应,求生之路2进不了服务器
  5. Excel 中的查找替换功能
  6. Obsidian 图片显示问题
  7. 【JZOJ4884】【NOIP2016提高A组集训第12场11.10】图的半径
  8. 转载linux一句话精彩问答
  9. Intel迅盘应用从入门到精通
  10. 【算法】Regular Expression Matching 正则匹配