排序算法总结分析(三)——吃货排序之烙饼排序

来源:互联网 发布:数据恢复 编辑:程序博客网 时间:2024/06/11 02:40

目录

排序算法总结分析(一)——开篇

排序算法总结分析(二)——常见八大排序算法

排序算法总结分析(三)——吃货排序之烙饼排序

今天先来个好玩点的,呃,确切说是好吃的点的问题。哈哈,就是如标题表明的烙饼排序。程序猿果然思维跟普通人就不一样,连吃个饼都想的这么多。问题描述是这样的:把一摞饼按照大小次序摆好,要求是小的在上面,大的在下面,只能通过翻转一摞饼进行排序,就像用铲子插入某个位置,把这个位置之上的所有饼进行翻转。那假设有N块大小不一的烙饼,最少要翻转几次才能达到最终有序排列呢?


翻转演示图

与传统排序不同的是,不能一张张抽出来,然后插入进去;也不能任意交换两块饼。说明基本的排序算法都不太好用。是不是有点意思呢?想当年比尔·盖茨也研究过这个问题~

目前这个问题的答案只有一个范围,没有确切的值,完成排序需要的最少次数在(15/14)N与(18/11)N之间。2011年的时候这个问题被定义为NP-Hard。什么是NP-Hard?NP(Non-Deterministic)官方定义是非确定性多项式。而非确定性是指,可用一定数量的运算去解决多项式时间内可解决的问题。例如,著名的推销员旅行问题(Travel Saleman Problem or TSP):假设一个推销员需要从香港出发,经过广州,北京,上海,…,等 n 个城市,最后返回香港。任意两个城市之间都有飞机直达,但票价不等。假设公司只给报销C元钱,问是否存在一个行程安排,使得他能遍历所有城市,而且总的路费小于C?推销员旅行问题显然是NP的。因为如果你任意给出一个行程安排,可以很容易算出旅行总开销。但是,要想知道一条总路费小于C的行程是否存在,在最坏情况下,必须检查所有可能的旅行安排!这将是个天文数字。


好了介绍性的东西说的差不多了,下面主要讲一下怎么个排法~

由于每次操作都是针对最上面的饼,如果最底层的饼已经排好序,然后就只需要处理上面的N-1个饼了。


翻转图

首先,经过两次翻转,最大的饼已经在最下面了。接着次大的饼也需要两次翻转,最后剩两张饼的时候只需要1次就可以,所以总的至少翻转次数为2(n-1)-1即2n-3。

当然这是这个问题解的一种上限,非最优。

那么下限呢?这里也只说一种简单的优化,非最佳。每一次翻转最多使得一个烙饼与大小跟它相邻的烙饼排到一起。如果当前状态N个烙饼中,有M对相邻的烙饼它们不相邻,那么至少需要M次才能排好。

下面稍微介绍下优化方法及算法的实现。

假如这堆烙饼中有好几个不同的部分相对有序,就可以先把小一些的烙饼翻转使其有序。这样就会减少翻转次数。可以考虑每次翻转的时候,把两个本来应该相邻的烙饼尽可能换到一起。这样,当所有的烙饼都换到一起之后。实际上就完成了排序。这样的话就会想到使用动态规划或者递归的方法来实现。可以从不同的翻转策略开始,递归所有可能性。这样,肯定能找到最优解。

代码如下:

#include <stdio.h>/************************************************************************//* 烙饼排序实现——By Sin_Geek 2014.04.12                               *//************************************************************************/class CPancakeSorting{public:CPancakeSorting(){m_nCakeCnt = 0;m_nMaxSwap = 0;}//计算烙饼翻转信息,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++){printf("%d", m_arrSwap[i]);}printf("\nSearch Times : %d\n", m_nSearch);printf("Total Swap times = %d\n", m_nMaxSwap);}private://初始化数组信息,pCakeArray 存储烙饼索引数组,nCakeCnt烙饼个数void Init(int* pCakeArray, int nCakeCnt){m_nCakeCnt = nCakeCnt;        //初始化m_CakeArray = new int[m_nCakeCnt];for (int i = 0; i < m_nCakeCnt; i++){m_CakeArray[i] = pCakeArray[i];}//设置最多交换次数信息m_nMaxSwap = UpBound(m_nCakeCnt);//初始化交换结果数组m_SwapArray = new int[m_nMaxSwap];//初始化中间交换结果信息m_ReverseCakeArray = new int[m_nCakeCnt];for (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 - 3;}//寻找当前翻转的下界int LowerBound(int* pCakeArray, int nCakeCnt){int t,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_arrSwap[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);}}bool IsSorted(int* pCakeArray, int nCakeCnt){for (int i = 1; i < m_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;}}private:int m_nCakeCnt;       //烙饼个数int m_nMaxSwap;       //最多交换次数int m_nSearch;        //当前搜索次数int* m_CakeArray;     //烙饼信息数组int* m_SwapArray;     //交换结果数组int* m_ReverseCakeArray;//当前翻转烙饼信息数组int* m_ReverseCakeArraySwap;//当前翻转烙饼交换结果数组int m_arrSwap[10];};void main(){int nCakeCnt = 10;int arrSwap[10] = {3,2,1,6,5,4,9,8,7,0} ;CPancakeSorting pan;pan.Run(arrSwap, nCakeCnt);pan.Output();}

当然还可以把这个问题更复杂化一点,假定每个烙饼都有一面是烤过的,在原来排序的结果上附加一个条件,就是让所有烤过的那一面都朝下~有兴趣的可以思考一下哦~~


8 0
原创粉丝点击