编程之美学习笔记(三):一摞烙饼的排序
問題描述
星期五的晚上,一幫同事在希格瑪大廈附近的“硬盤酒吧”多喝了幾杯,程序員多喝了幾杯之后談什么呢?自然是算法
問題。有個同事說:
“我以前在餐廳打工,顧客經常點非常多的烙餅。店里的烙餅大小不一,我習慣在到達顧客飯桌前,把一摞餅按照大小
次序擺好---小的在上面,大的在下面。由于我一只手托著盤子,只好用另一只手,一次抓住最上面的幾塊餅,把它們
上下顛倒個個兒,反復幾次之后,這摞烙餅就排好序了。我后來想,這實際上是個有趣的排序問題:假設有n塊大小
不一的摞餅,那最少要翻幾次,才能達到大小有序的結果呢?”
從這段描述中我們可以很容易的就把問題抽象出來:給你一個由 n 個連續整數組成的數組,數組是無序的,現在要你
對這個數組進行升序排序,但只能對數組進行一種操作,就是只能對從數組第一個元素開始到數組中任意一個元素之
間的所有元素進行翻轉。只是這樣的話,問題還不是很麻煩。這里還要求我們寫程序輸出最優的翻轉過程。
思路:
一般遇到最優解問題我們首先想到的就是動態規劃、貪心以及分支界限三種方法。書中提到了動態規劃和分支界限這
兩種方法,但只給出了分支界限方法的思路以及代碼,所以我就以書上的方法來講講自己的理解吧。
書上的代碼用的是遞歸遍歷+剪枝。既然是遞歸,就得有個退出的條件,不然就死循環了。在這個問題中,第一個遞
歸退出的條件理所當然就是所有的烙餅都已經排好序了。如果只是這樣的,就不涉及到剪枝了。我們知道遞歸的特點
是簡潔,但是其效率相對來說是比較低的,如果遇到很大的問題,在現實中就不適用了,而提高遞歸效率的唯一方法
就是剪枝,所以我們必須想辦法找到剪枝的策略。
一般來說我們都是通過去除一些不必要的遍歷來進行剪枝的,分析這個問題,我們不難發現,最多要翻轉的次數不會
超過2*(n-1)次,至于這個數字是怎么來的,書上已經寫的很明白了,這里就不多作累贅啦。如果只是等某種翻轉次數
超過2*(n-1)再來放棄這種遍歷的話,顯然意義不大。按照書上給出的思路,以2*(n-1)作為上界(UpperBound),表
示最差方案;而下界(LowerBound)則是動態的,下界的取值是當前排序狀態下,我們至少還要翻轉的次數,程序
中我們用變量 nEstimate ?表示。當當前翻轉次數+nEstimate>UpperBound()時,說明繼續遍歷下去最后的結果會超過
上界,這時我們直接對其進行剪枝。
代碼:
#include<cassert> #include<iostream> using namespace std;class PrefixSorting {int* m_CakeArray; //烙餅信息數組int m_nCakeCnt; //烙餅個數int m_nMaxSwap; //最多翻轉次數,即上界int* m_SwapArray; //交換結果數組,即保存最優解的每一步交換int* m_ReverseCakeArray; //當前翻轉烙餅信息數組int* m_ReverseCakeArraySwap; //當前翻轉烙餅交換結果數組int m_nSearch; //當前遍歷次數信息 public:PrefixSorting(){m_nCakeCnt = 0;m_nMaxSwap = 0;}~PrefixSorting(){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;}/*計算烙餅翻轉信息*/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\n", m_SwapArray[i]);printf("Search Times : %d\n", m_nSearch);printf("Total Swap times = %d\n", m_nMaxSwap);}private:void 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 = UpperBound (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];}/*計算上界*/int UpperBound(int nCakeCnt){return (nCakeCnt - 1) * 2; //書上這里是 nCakeCnt * 2}/*計算下界*/int LowerBound(int* pCakeArray, int nCakeCnt){int t;int 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_SwapArray[i] = m_ReverseCakeArraySwap[i];}return ;}/*遞歸遍歷*/for (int i = 1; i < m_nCakeCnt; i++){Reverse (0, i);m_ReverseCakeArraySwap[step] = i;Search (step + 1);Reverse (0, i);}}/*判斷是否已經排好序*/bool IsSorted(int* pCakeArray, int nCakeCnt){for (int i = 1; i < nCakeCnt; 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;}} };int main() {int a[] = {3, 2, 1, 6, 5, 4, 9, 8, 7, 0};PrefixSorting test;test.Run(a, 10);test.Output();system("pause");return 0; }
總結
以上是生活随笔為你收集整理的编程之美学习笔记(三):一摞烙饼的排序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android qq接口,手机QQ Sc
- 下一篇: 参考文献中的[EB/OL]表示什么含义?