每一个算法都值得好好地分析
问题:假设有n块大小不一的烙饼,那最少要翻几次,才能达到最后大小有序的结果呢?
分析与解法
这个排序问题非常有意思,首先我们要弄清楚解决问题的关键操作——“单手每次抓几块饼,全部颠倒”。
每次我们只能选择最上方的一堆饼,一起翻转。而不能一张张地直接抽出来,然后进行插入,也不能交换任意两块饼子。这说明基本的排序办法都不太好用。那么怎么把这n个烙饼排好序呢?
由于每次操作都是针对最上面的饼,如果最底层的饼已经排序,那我们只用处理上面的n-1个烙饼。这样,我们可以再简化为n-2、n-3,直到最上面的两个饼排好序。
【解法一】
我们用图1-7演示一下,为了把最大的烙饼摆在最下面,我们先把最上面的烙饼和最大的烙饼之间的烙饼翻转(1~4之间),这样,最大的烙饼就在最上面了。接着,我们把所有烙饼翻转(4~5之间),最大的烙饼就摆在最下面了。
两次翻转烙饼,调整最大的烙饼到最底端
之后,我们对上面n-1、n-2个饼重复这个过程就可以了。
那么,我们一共需要多少次翻转才能把这些烙饼给翻转过来呢?
首先,经过两次翻转可以把最大的烙饼翻转到最下面。因此,最多需要把上面的n-1个烙饼依次翻转两次。那么,我们至多需要2(n-1)次翻转就可以把所有烙饼排好序(因为第二小的烙饼排好的时候,最小的烙饼已经在最上面了)。
这样看来,单手翻转的想法是肯定可以实现的。我们进一步想想怎么减少翻转烙饼的次数吧。
怎样才能通过程序来搜索到一个最优的方案呢?
首先,通过每次找出最大的烙饼进行翻转是一个可行的解决方案。那么,这个方案是最好的一个吗?考虑这样一种情况,假如这堆烙饼中有好几个不同的部分相对有序,凭直觉来猜想,我们可以先把小一些的烙饼进行翻转,让其有序。这样会比每次翻转最大的烙饼要更快。
既然如此,有类似的方案可以达到目的吗?比如说,考虑每次翻转的时候,把两个本来应该相邻在烙饼尽可能地换到一起。这样,当所有的烙饼都换到一起之后,实际上就是完成排序了。(从这个意义上来说,每次翻最大烙饼的方案实质上就是每次把最大的和次大的交换到一起。)
在这样的基础之上,本能的一个想法就是穷举。只要穷举出所有可能的交换方案,那么,我们一定能够找到一个最优的方案。
沿着这个思路去考虑,我们自然就会使用动态规划或者递归的方法来进行实现了。可以从不同的翻转策略开始,比如说第一次先翻最小的,然后递归把所有的可能全部翻转一遍。这样,最终肯定是可以找到一个解的。
但是,既然是递归就一定有退出的条件。在这个过程中,第一个退出的条件肯定是所有的烙饼已经排好序。那么,有其他的吗?如果大家仔细想想就会发现到,既然2(n-1)是一个最多的翻转次数。如果在算法中,需要翻转的次数多于2(n-1),那么,我们就应该放弃这个翻转算法,直接退出。这样,就能够减少翻转的次数。
从另外一个层面上来说,既然这是一个排序问题。我们也应该利用到排序的信息来进行处理。同样,在翻转的过程中,我们可以看看当前的烙饼数组的排序情况如何,然后利用这些信息来帮助减少翻转次数的判断过程。
下面是在前面讨论的基础之上形成的一个粗略的搜索最优方案的程序:
class CFlapjackSorting
{
public:
int *m_CakeArray; //烙饼信息数组
int m_nCakeCnt; //烙饼个数
int m_nMaxSwap; //最多交换次数。根据前面的判断,这里最多为m_nCakeCnt*2
int * m_SwapArray; //交换结果数组
int * m_ReverseCakeArray; //当时翻转烙饼信息数组
int * m_ReverseCakeArraySwap;//当前翻转烙饼交换结果数组
int m_nSearch;//当前搜索次数信息
//int * m_arrSwap;
public:
CFlapjackSorting(void);
public:
~CFlapjackSorting(void);
void Run(int *pCakeArray,int nCakeCnt);
void Output();
public:
//初始化数组信息
//pCakeArray 存储烙饼索引数组
//nCakeCnt 烙饼个数
void Init(int *pCakeArray,int nCakeCnt);
//寻找当前翻转的上界
int UpBound(int nCakeCnt);
//寻找当前翻转的下界
int LowerBound(int * pCakeArray,int nCakeCnt);
//排序的主函数
void Search (int step);
//true:已经排好序
//false:未排序
bool IsSorted(int * pCakeArray,int nCakeCnt);
//翻转烙饼信息
void Revert(int nBegin ,int nEnd);
};
CFlapjackSorting::CFlapjackSorting(void)
{
m_nCakeCnt=0;
m_nMaxSwap=0;
}
CFlapjackSorting::~CFlapjackSorting(void)
{
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;
}
/*if(m_arrSwap!=NULL)
{
delete m_arrSwap;
}*/
}
//寻找当前翻转的上界
int CFlapjackSorting::UpBound(int nCakeCnt)
{
return nCakeCnt*2;
}
//初始化数组信息
//pCakeArray 存储烙饼索引数组
//nCakeCnt 烙饼个数
void CFlapjackSorting::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];
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];
//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];
////m_arrSwap=new int(m_nMaxSwap);
}
//寻找当前翻转的下界
int CFlapjackSorting::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 CFlapjackSorting::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;
}
}
//true:已经排好序
//false:未排序
bool CFlapjackSorting::IsSorted(int * pCakeArray,int nCakeCnt)
{
for(int i=1;i<nCakeCnt;i++)
{
if(pCakeArray[i-1]>pCakeArray[i])
{
return false;
}
}
return true;
}
// 排序的主函数
void CFlapjackSorting::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(i = 1; i < m_nCakeCnt; i++)
{
Revert(0, i);
m_ReverseCakeArraySwap[step] = i;
Search(step + 1);
Revert(0,i);
}
}
//计算烙饼翻转信息
//pCakeArray 存储烙饼索引数组
//nCakeCnt 烙饼个数
void CFlapjackSorting::Run(int *pCakeArray,int nCakeCnt)
{
Init(pCakeArray,nCakeCnt);
m_nSearch=0;
Search(0);
}
//输出烙饼具体翻转的次数
void CFlapjackSorting::Output()
{
for(int i = 0; i < m_nMaxSwap; i++)
{
printf("%d ", m_SwapArray[i]);
}
printf("/n |Search Times| : %d/n", m_nSearch);
printf("Total Swap times = %d/n", m_nMaxSwap);
/*for(int i=0;i<m_nMaxSwap;i++)
{
printf("%d ",m_SwapArray[i]);
}
printf("/n |Search Times |:%d/n",m_nSearch);
printf("Total Swap times= %d/n",m_nMaxSwap);*/
}
使用这个类的时候直接调用就可以了