C++一摞烙饼的问题

来源:互联网 发布:超星雕刻机怎么编程 编辑:程序博客网 时间:2024/04/27 18:46

问题:假设有n块大小不一的烙饼,那单手最少要翻几次,才能达到最后大小有序的结果呢?

分析与解法

这个排序问题非常有意思,首先我们要弄清楚解决问题的关键操作——“单手每次抓几块饼,全部颠倒”。

每次我们只能选择最上方的一堆饼,一起翻转。而不能一张张地直接抽出来,然后进行插入,也不能交换任意两块饼子。这说明基本的排序办法都不太好用。那么怎么把这n个烙饼排好序呢?

由于每次操作都是针对最上面的饼,如果最底层的饼已经排序,那我们只用处理上面的n-1个烙饼。这样,我们可以再简化为n-2、n-3,直到最上面的两个饼排好序。

【解法一】

我们用图1演示一下,为了把最大的烙饼摆在最下面,我们先把最上面的烙饼和最大的烙饼之间的烙饼翻转(1~4之间),这样,最大的烙饼就在最上面了。接着,我们把所有烙饼翻转(4~5之间),最大的烙饼就在最底下了。

两次翻转烙饼,调整最大的烙饼到最底端

之后,我们对上面n-1、n-2个饼重复这个过程就可以了。

那么,我们一共需要多少次翻转才能把这些烙饼给翻转过来呢?

首先,经过两次翻转可以把最大的烙饼翻转到最下面。因此,最多需要把上面的n-1个烙饼依次翻转两次。那么,我们至多需要2(n-1)次翻转就可以把所有烙饼排好序(因为第二小的烙饼排好的时候,最小的烙饼已经在最上面了)。

这样看来,单手翻转的想法是肯定可以实现的。我们进一步想想怎么减少翻转烙饼的次数吧。

怎样才能通过程序来搜索到一个最优的方案呢?

首先,通过每次找出最大的烙饼进行翻转是一个可行的解决方案。那么,这个方案是最好的一个吗?考虑这样一种情况,假如这堆烙饼中有好几个不同的部分相对有序,凭直觉来猜想,我们可以先把小一些的烙饼进行翻转,让其有序。这样会比每次翻转最大的烙饼要更快。

既然如此,有类似的方案可以达到目的吗?比如说,考虑每次翻转的时候,把两个本来应该相邻在烙饼尽可能地换到一起。这样,当所有的烙饼都换到一起之后,实际上就是完成排序了。(从这个意义上来说,每次翻最大烙饼的方案实质上就是每次把最大的和次大的交换到一起。)

在这样的基础之上,本能的一个想法就是穷举。只要穷举出所有可能的交换方案,那么,我们一定能够找到一个最优的方案。

沿着这个思路去考虑,我们自然就会使用动态规划或者递归的方法来进行实现了。可以从不同的翻转策略开始,比如说第一次先翻最小的,然后递归把所有的可能全部翻转一遍。这样,最终肯定是可以找到一个解的。

但是,既然是递归就一定有退出的条件。在这个过程中,第一个退出的条件肯定是所有的烙饼已经排好序。那么,有其他的吗?如果大家仔细想想就会发现到,既然2(n-1)是一个最多的翻转次数。如果在算法中,需要翻转的次数多于2(n-1),那么,我们就应该放弃这个翻转算法,直接退出。这样,就能够减少翻转的次数。

从另外一个层面上来说,既然这是一个排序问题。我们也应该利用到排序的信息来进行处理。同样,在翻转的过程中,我们可以看看当前的烙饼数组的排序情况如何,然后利用这些信息来帮助减少翻转次数的判断过程。

下面是在前面讨论的基础之上形成的一个粗略的搜索最优方案的程序:

c++实现代码如下:

CPrefixSorting_vector.h:

#pragma  once#include <iostream>#include <assert.h>#include <vector>using namespace std;class CPrefixSorting{public:vector<int> m_CakeArray;   //存储烙饼索引数组int m_nCakeCut;     //烙饼个数int m_nMaxSwap;  //最多交换次数信息vector<int> m_SwapArray;                            //交换结果数组vector<int> m_ReverseCakeArray;              //当前翻转烙饼信息数组vector<int> m_ReverseCakeArraySwap;    //当前翻转烙饼交换结果数组int m_nSearch;                                  //当前搜索次数信息           public:CPrefixSorting(){m_nCakeCut=0;m_nMaxSwap=0;}~CPrefixSorting(){if (m_CakeArray.size()!=0){m_CakeArray.clear();}if (m_SwapArray.size()!=0){   m_SwapArray.clear();}if (m_ReverseCakeArray.size()!=0){m_ReverseCakeArray.clear();}if (m_ReverseCakeArraySwap.size()!=0){m_ReverseCakeArraySwap.clear();}}/*计算烙饼翻转信息@parampCakeArray:存储烙饼索引数组nCakeCut:烙饼个数*/void Run(vector<int>&pCakeArray,int nCakeCut){Init(pCakeArray,nCakeCut);m_nSearch=0;Search(0);}/*输出烙饼具体翻转次数*/void Output(){for (int i=0;i<m_nMaxSwap;i++){cout<<m_SwapArray[i]<<" ";}cout<<"Search time: "<<m_nSearch;cout<<"Total Swap times="<<m_nMaxSwap<<endl;/*for (int j=0;j<m_nCakeCut;j++){cout<<m_ReverseCakeArray[j]<<" ";}cout<<endl;for (int j=0;j<m_nMaxSwap;j++){cout<<m_ReverseCakeArraySwap[j]<<" ";}cout<<endl;for (int j=0;j<m_nMaxSwap+1;j++){cout<<m_SwapArray[j]<<" ";}cout<<endl;*/}private:/*初始化数组信息@parampCakeArray:存储烙饼索引数组nCakeCut:烙饼个数*/void Init(vector<int >&pCakeArray,int nCakeCut){assert(pCakeArray.size()!=0);assert(nCakeCut>0);m_nCakeCut=nCakeCut;//初始化烙饼数组vector<int>step0(m_nCakeCut,0);m_CakeArray=step0;cout<<"input:"<<endl;for (int i=0;i<m_nCakeCut;i++){m_CakeArray[i]=pCakeArray[i];cout<<m_CakeArray[i]<<" ";}cout<<endl;//设置最多交换次数m_nMaxSwap=UpperBound(m_nCakeCut);//初始化交换结果数组vector<int>step1(m_nMaxSwap+1,0);m_SwapArray=step1;assert(m_SwapArray.size()!=0);//初始化中间交换结果信息m_ReverseCakeArray=step0;for (int i=0;i<m_nCakeCut;i++){m_ReverseCakeArray[i]=m_CakeArray[i];}vector<int>step2(m_nMaxSwap+1,0);m_ReverseCakeArraySwap=step2;}/*寻找当前翻转上届*/int UpperBound(int nCakeCut){return nCakeCut*2;}/*寻找当前翻转下界*/int LowerBound(vector<int>& pCakeArray,int  nCakeCut){int t,ret=0;for (int i=1;i<nCakeCut;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_nCakeCut);if (step+nEstimate>m_nMaxSwap){return;}if (IsSorted(m_ReverseCakeArray,m_nCakeCut)){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_nCakeCut;i++){Reverse(0,i);m_ReverseCakeArraySwap[step]=i;Search(step+1);Reverse(0,i);}}/*true:已经排好序false:未排序*/bool IsSorted(vector<int>& pCakeArray,int nCakeCut){for (int i=1;i<m_nCakeCut;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;}}};
main.cpp:
#include "CPrefixSorting_vector.h"void main(){CPrefixSorting cps;  int cake[] = {3, 2, 1, 6, 5, 4, 9, 8, 7, 0};  vector<int> cake1(cake,cake+10);cps.Run(cake1,cake1.size());  cps.Output();  return ;}

程序运行结果如下:

input:

3 2 1 6 5 4 9 8 7 0

翻转的位置依次为

4 8 6 8 4 9

Search time:172126

total Swap times=6

最优翻转过程如下:

5 6 1 2 3 4 9 8 7 0

7 8 9 4 3 2 1 6 5 0

1 2 3 4 9 8 7 6 5 0

5 6 7 8 9 4 3 2 1 0

9 8 7 6 5 4 3 2 1 0

0 1 2 3 4 5 6 7 8 9

 

 

0 0
原创粉丝点击