编程之美—烙饼排序问题(JAVA)
一、问题描述
星期五的晚上,一帮同事在希格玛大厦附近的“硬盘酒吧”多喝了几杯。程序员多喝了几杯之后谈什么呢?自然是算法问题。有个同事说:“我以前在餐 馆打工,顾客经常点非常多的烙饼。店里的饼大小不一,我习惯在到达顾客饭桌前,把一摞饼按照大小次序摆好——小的在上面,大的在下面。由于我 一只手托着盘子,只好用另一只手,一次抓最上面的几块饼,把它们上下颠倒个个儿,反复几次之后,这摞烙饼就排好序了。我后来想,这实际上是个 有趣的排序问题:假设有n块大小不一的饼,那最少要翻几次,才能达到最后大小有序的结果呢?你能否写出一个程序,对于n块大小不一的烙饼,输出 最优化的翻饼过程呢?
二、问题分析
首先看到这个问题 ,你第一感觉肯定是:噢,这是一个排序问题。但是这个问题不同于一般的排序,从问题描述中可以看出,每次操作都只能选从第 一个到第n个烙饼,然后整体倒置(如图1)。问题来了,选择从第一个到底n个烙饼,进行倒置(1~n个饼并没有排好序),如何在每一次或者几次 倒置中可以使得烙饼的局部排好序呢?然后经过一系列倒置,然后使整体排好序。描述到这里,你是不是已经想到了一种解法呢?
三、解法一:首先把最上面的烙饼和最大的烙饼之间的烙饼翻转,于是,最大的烙饼就在最上面了,再进行一次翻转,把最大的烙饼放在最底
部,最大的烙饼就在自己的正确位置上了。即通过两次翻转,把最大的烙饼放在最低部了。然后选择从最上面的饼和次大的烙饼
,重复上面的步骤。每两次翻转,把不再正确位置上的最大饼放在合适的位置上。
那么,我们一共需要多少次翻转才能把这些烙饼给翻转过来呢?
首先,经过两次翻转可以把最大的烙饼翻转到最下面。因此,最多需要把上面的n-1个烙饼依次翻转两次。那么,排到剩下第一个和第二个时, 我们需要2(n-2)次翻转。最后,第一个和第二个最多需要一次翻转(可能一次都不需要,已经排好序了)。
四、我们已经求出了一个解法,但是会不会有更好的方法通过更少的翻转达到相同的目的呢?
考虑一种情况,比如上面的图1,序号1、2、3的最上面三个烙饼其实是三个相邻顺序已经排好的烙饼。考虑每次翻转时都让把两个本来应该相邻在烙饼尽可能地换到一起。这样,当所有的烙饼都换到一起之后,实际上就是完成排序了。(从这个意义上来说,每次翻最大烙饼的方案实质上就是每次把最大的和次大的交换到一起,但是通过了两次翻转,不是每一次都尽可能的把两个本来应该相邻在烙饼尽可能地换到一起)
那什么的情况下可以通过最少的翻转排好序呢?这里是不是可以选择一种穷举的方法,穷举所有的的每次翻转的情况,展开来看就是一棵树,每个结点都有九个子节点(有九中翻转情况),如果翻转达到2n-3还未排好序则截断这一分支。可以根据当前的翻转次数加上估计还需要的最小翻转次数(下界值,下限值可以这样确定:从最后一个位置开始,往前找到第一个与最终结果位置不同的烙饼编号(也就是说排除最后几个已经就位的烙饼),从该位置到第一个位置,计算相邻的烙饼的编号不连续的次数,再加上1。)来判断是否截断这一分支。我们可以选择递归的方法深度遍历这颗树,得到最小的排好序的那些步骤。
五、代码
- public class CFlapjackSorting {
- private int m_nCake; //烙饼的个数
- private int[] m_CakeArray; //烙饼的信息数组,也就是初始数组的输入数
- private int m_nMaxSwap; //最大的交换次数
- private int[] m_SwapArray; //存储交换位置信息的数组
- private int[] m_ReverseCakeArray; //当时翻转烙饼信息数组
- private int[] m_ReverseCakeArraySwap; //当时存储交换位置信息的数组
- private int m_nSearch; //搜索次数
- public CFlapjackSorting() {
- m_nCake = 0;
- m_nMaxSwap = 0;
- }
- /**
- *
- * @param pCakeArray 输入的烙饼信息
- * @param nCake 数组的长度
- */
- public void init(int[] pCakeArray, int nCake) {
- assert (pCakeArray != null);
- m_nCake = nCake;
- m_CakeArray = new int[m_nCake];
- assert (m_CakeArray != null);
- //初始化烙饼数组
- for (int i = 0; i < m_nCake; i++) {
- m_CakeArray[i] = pCakeArray[i];
- }
- m_nMaxSwap = upBound(m_nCake);
- m_SwapArray = new int[m_nMaxSwap + 1];
- assert (m_SwapArray != null);
- m_ReverseCakeArray = new int[m_nCake];
- for (int i = 0; i < m_nCake; i++) {
- m_ReverseCakeArray[i] = m_CakeArray[i];
- }
- m_ReverseCakeArraySwap = new int[m_nMaxSwap+1];
- }
- /**
- * 最大上界:可以翻转的最大次数
- * @param nCake 烙饼的个数
- * @return
- */
- public int upBound(int nCake) {
- return 2 * nCake;
- }
- /**
- * 最小下界
- * @param pCakeArray
- * @param nCake
- * @return
- */
- int LowerBound(int[] pCakeArray, int nCake) {
- int t, lower = 0;
- for (int i = 1; i < nCake; i++) {
- t = pCakeArray[i] - pCakeArray[i - 1];
- if ((t == 1) || (t == -1)) {
- } else {
- lower++;
- }
- }
- return lower;
- }
- /**
- * 搜索函数
- * @param step 第几步
- */
- public void search(int step) {
- int minEstimate; //最小交换次数估计
- m_nSearch++;
- minEstimate = LowerBound(m_ReverseCakeArray, m_nCake);
- if (step + minEstimate > m_nMaxSwap)
- return;
- //判读是否排好序
- if (isSorted(m_ReverseCakeArray, m_nCake)) {
- //如果排好序了,而且翻转次数小于最大翻转次数。否则终止搜索
- if (step < m_nMaxSwap) {
- m_nMaxSwap = step;
- for (int i = 0; i < m_nMaxSwap; i++) {
- m_SwapArray[i] = m_ReverseCakeArraySwap[i];
- }
- }
- return;
- }
- //进行翻转
- for (int i = 1; i < m_nCake; i++) {
- revert(0, i);
- m_ReverseCakeArraySwap[step] = i; //记录每次翻转时翻转的位置信息
- search(step + 1);
- revert(0, i);
- }
- }
- /**
- * 判断是否排好序
- * @param pCakeArray
- * @param nCake
- * @return
- */
- public boolean isSorted(int[] pCakeArray, int nCake) {
- for (int i = 1; i < nCake; i++) {
- if (pCakeArray[i] < pCakeArray[i - 1]) {
- return false;
- }
- }
- return true;
- }
- public void revert(int nBegin, int nEnd) {
- int t;
- for (int i = nBegin, j = nEnd; i < j; i++, j--) {
- t = m_ReverseCakeArray[i];
- m_ReverseCakeArray[i] = m_ReverseCakeArray[j];
- m_ReverseCakeArray[j] = t;
- }
- }
- /**
- * 输出翻转的位置信息
- * 搜索的次数
- * 翻转的次数
- * 翻转的每一步翻转情况
- */
- public void output() {
- for (int i = 0; i < m_nMaxSwap; i++) {
- System.out.printf("%d", m_SwapArray[i]);
- }
- System.out.printf("\n|Search Times|:%d\n", m_nSearch);
- System.out.printf("Total swap times = %d\n", m_nMaxSwap);
- perReverseArrayOutput(m_CakeArray,m_SwapArray,m_nCake,m_nMaxSwap);
- }
- /**
- * 通过记录每次翻转位置的数组,输入每次翻转的数组
- * 显示每一步的翻转结果
- * @param cakeArray 最初要排序的数组
- * @param swapArray 记录每次翻转时翻转的位置
- *@param nCake 数组的数量
- * @param maxSwap 翻转的次数
- */
- public static void perReverseArrayOutput(int[] cakeArray,int[] swapArray,int nCake,int maxSwap){
- System.out.printf("%d\n", maxSwap);
- int t;
- for(int i=0;i<maxSwap;i++) {
- System.out.printf("%d ",swapArray[maxSwap]);
- for (int x = 0,y = swapArray[i]; x < y; x++, y--) {
- t = cakeArray[x];
- cakeArray[x] = cakeArray[y];
- cakeArray[y] = t;
- }
- for(int k=0;k<nCake;k++){
- System.out.printf("%d ",cakeArray[k]);
- }
- System.out.print("\n");
- }
- }
- public void run(int[] pCakeArray,int nCake){
- init(pCakeArray,nCake);
- m_nSearch=0;
- search(0);
- }
- }
六、参考博客
jinLei_zhao的博客(一摞烙饼的排序)
Huaerge的博客(一摞烙饼的排序)
编程之美—烙饼排序问题(JAVA)的更多相关文章
- java并发编程之美-阅读记录1
1.1什么是线程? 在理解线程之前先要明白什么是进程,因为线程是进程中的一个实体.(线程是不会独立存在的) 进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程中的 ...
- Java编程思想重点笔记(Java开发必看)
Java编程思想重点笔记(Java开发必看) Java编程思想,Java学习必读经典,不管是初学者还是大牛都值得一读,这里总结书中的重点知识,这些知识不仅经常出现在各大知名公司的笔试面试过程中,而 ...
- 【编程之美】2.5 寻找最大的k个数
有若干个互不相等的无序的数,怎么选出其中最大的k个数. 我自己的方案:因为学过找第k大数的O(N)算法,所以第一反应就是找第K大的数.然后把所有大于等于第k大的数取出来. 写这个知道算法的代码都花了2 ...
- 【编程之美】CPU
今天开始看编程之美 .第一个问题是CPU的使用率控制,微软的问题果然高大上,我一看就傻了,啥也不知道.没追求直接看答案试了一下.发现自己电脑太好了,4核8线程,程序乱飘.加了一个进程绑定,可以控制一个 ...
- 《大道至简》之第一章:编程的精义读后感(JAVA伪代码)
——大道至简之编程的精义读后感(JAVA伪代码) import.java.大道至简.*; import.java.愚公移山.*; public class YuGongYiShan{ 愚公 = {项目 ...
- 编程之美_1.1 让CPU占用率曲线听你指挥
听到有人说让要写一个程序,让用户来决定Windows任务管理器的CPU占用率. 觉得很好奇.但第一个想法就是写个死循环.哈哈.不知道具体的占用率是多少,但至少能保证在程序运行时,CPU的占用率终会稳定 ...
- 编程之美的2.17,数组循环移位 & 字符串逆转(反转) Hello world Welcome => Welcome world Hello
代码如下:(类似于编程之美的2.17,数组循环移位) static void Main(string[] args) { string input = "Hello World Welcom ...
- [质疑]编程之美求N!的二进制最低位1的位置的问题
引子:编程之美给出了求N!的二进制最低位1的位置的二种思路,但是呢?但是呢?不信你仔细听我道来. 1.编程之美一书给出的解决思路 问题的目标是N!的二进制表示中最低位1的位置.给定一个整数N,求N!二 ...
- JNI编程(二) —— 让C++和Java相互调用(2)
3.getRamdomOrder() 这个方法会从backend得到一个随机的Order对象(抱歉这里“Random”拼错了),然后再调用java中相应的通知方法来通知foreground.getRa ...
随机推荐
- 【趣事】用 JavaScript 对抗 DDOS 攻击
继续趣事分享. 上回聊到了大学里用一根网线发起攻击,今天接着往后讲. 不过这次讲的正好相反 -- 不是攻击,而是防御.一个奇葩防火墙的开发经历. 第二学期大家都带了电脑,于是可以用更高端的方法断网了. ...
- Castle Core 4.0.0 alpha001发布
时隔一年多以后Castle 项目又开始活跃,最近刚发布了Castle Core 4.0.0 的alpha版本, https://github.com/castleproject/Core/releas ...
- 如何安全的将VMware vCenter Server使用的SQL Server Express数据库平滑升级到完整版
背景: 由于建设初期使用的vSphere vCenter for Windows版,其中安装自动化过程中会使用SQL Server Express的免费版数据库进行基础环境构建.而此时随着业务量的增加 ...
- css text-fill-color与text-stroke讲解
顾名思义"text-fill-color"就是文字填充颜色而"text-stroke"就是文字描边.还别说,两个属性可以制作出各种炫酷的文字效果,不过IE系列都 ...
- Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- Entity Framework的启动速度优化
最近开发的服务放到IIS上寄宿之后,遇到一些现象,比如刚部署之后,第一次启动很慢:程序放置一会儿,再次请求也会比较慢.比如第一个问题,可以解释为初次请求某一个服务的时候,需要把程序集加载到内存中可能比 ...
- 使用rowid抽取数据方法以及大数据量游标卡住的应对
平时工作的时候,经常会遇到这种事情,从一个大表A中,抽取字段a在一个相对较小B的表的数据,比如,从一个详单表中,抽取几万个用户号码的话单出来.这种时候,一般来说, 做关联查询: create tabl ...
- 【从零开始学BPM,Day2】默认表单开发
[课程主题]主题:5天,一起从零开始学习BPM[课程形式]1.为期5天的短任务学习2.每天观看一个视频,视频学习时间自由安排. [第二天课程] Step 1 软件下载:H3 BPM10.0全开放免费下 ...
- SMBus set up a 2-byte EEPROM address for read/write
Sequencer Engine spec: http://www.analog.com/media/en/technical-documentation/data-sheets/ADM1260.pd ...
- Centos6.5 配置Nginx开机自启动
1.在/etc/init.d/目录下创建 nginx 文件,内容如下: #!/bin/sh # # nginx - this script starts and stops the nginx dae ...