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