《编程之美》一摞烙饼问题探讨
这类问题我最早遇到是厨师摆盘子问题,就是厨师要把一个架子上的盘子按照从大到小排列,只能象本题中翻动烙饼的方式翻动盘子,当时我给出了这样的答案:
int FindMaxIdx(int *pDishes, int nBegin, int nEnd)
{
int i,maxIdx = nBegin;
for(i = nBegin + 1; i <= nEnd; i++)
{
if(pDishes[i] > pDishes[maxIdx])
{
maxIdx = i;
}
}
return maxIdx;
}
void Revert(int *pDishes, int nBegin, int nEnd)
{
int i,j,tmp;
assert(nEnd > nBegin);
for(i = nBegin, j = nEnd; i < j; i++,j–)
{
tmp = pDishes[i];
pDishes[i] = m_CakeArray[j];
pDishes[j] = tmp;
}
}
int PrefixSort(int *pDishes, int fjCount)
{
int i,curMax;
int swapCount = 0;
for(i = fjCount – 1; i > 0; i–)
{
curMax = FindMaxIdx(pDishes, 0, i);
if(curMax == 0)
{
Revert(pDishes, 0, i);
swapCount++;
}
else if(curMax != i)
{
Revert(pDishes, 0, curMax);
Revert(pDishes, 0, i);
swapCount += 2;
}
else
{
;
}
}
return swapCount;
}
PrefixSort()函数排序数组并返回翻转的次数,这段代码的思路就是从放最大盘子的位置开始,首先找到在这个位置之前的最大盘子的位置,然后利用一到两次翻转将最大的盘子换到这个位置,然后向前一个位置移位并重复上面的过程。在相当长的一段时间内我都认为这就是最优解,因为它符合翻转次数小于2*(fjCount – 1),并且对局部有序的盘子还能避免翻转,直到我的同学告诉我局部有序的盘子如果和你排序的方向是反的情况呢?也就是说对有几个盘子已经是大小有序,只是顺序和要排序的方向是相反的这种情况如何利用?局部有序但是反序是否有方法可以减少翻转次数呢?后来一直没有想到好的方法,只好用穷举法解决了这个问题,《算法导论》将这种穷举+条件判断的方法称之为分枝界限法。后来看到一篇博士论文,称解决这种最优化问题还可以用动态规划方法,我从《算法导论》查到了对动态规划的描述,利用动态规划求解问题需要问题域存在最优子问题,通过最优子问题的局部最优解堆叠出整个问题域的最优解,不过后来一直没有找到分解最优子问题的方法,只好作罢。在书店翻这本书的时候看到书中提到可以使用动态规划的方法,于是很期待,可是回家一看最终的解法还是采用穷举所有解的方法,很失望,不知道谁能出手指点一下如何用动态规划求解这个问题。
前文说过,本书的勘误之多也是出类拔萃的,本例的代码更是如此,m_arrSwap和m_n可以理解是印刷错误,但是Init()函数中刷刷刷刷 new 了四块内存,始终没看到有释放它们的地方,就想不通是怎么回事儿了,写这段代码兄弟的不会是Java用习惯了吧?虽然官方给出了勘误,增加了一个析构函数释放了内存,不过可以看出来作者好像心不在蔫,至少也应该在构造函数中给这四个指针初始化为NULL吧?