標籤:最佳化 turn max 意義 http 計算 部落格 htm csdn
一、問題描述
星期五的晚上,一幫同事在希格瑪大廈附近的“硬碟酒吧”多喝了幾杯。程式員多喝了幾杯之後談什麼呢?自然是演算法問題。有個同事說:“我以前在餐 館打工,顧客經常點非常多的烙餅。店裡的餅大小不一,我習慣在到達顧客飯桌前,把一摞餅按照大小次序擺好——小的在上面,大的在下面。由於我 一隻手托著盤子,只好用另一隻手,一次抓最上面的幾塊餅,把它們上下顛倒個個兒,反覆幾次之後,這摞烙餅就排好序了。我後來想,這實際上是個 有趣的排序問題:假設有n塊大小不一的餅,那最少要翻幾次,才能達到最後大小有序的結果呢?你能否寫出一個程式,對於n塊大小不一的烙餅,輸出 最佳化的翻餅過程呢?
二、問題分析
首先看到這個問題 ,你第一感覺肯定是:噢,這是一個排序問題。但是這個問題不同於一般的排序,從問題描述中可以看出,每次操作都只能選從第 一個到第n個烙餅,然後整體倒置(1)。問題來了,選擇從第一個到底n個烙餅,進行倒置(1~n個餅並沒有排好序),如何在每一次或者幾次 倒置中可以使得烙餅的局部排好序呢?然後經過一系列倒置,然後使整體排好序。描述到這裡,你是不是已經想到了一種解法呢?
三、解法一:首先把最上面的烙餅和最大的烙餅之間的烙餅翻轉,於是,最大的烙餅就在最上面了,再進行一次翻轉,把最大的烙餅放在最底
部,最大的烙餅就在自己的正確位置上了。即通過兩次翻轉,把最大的烙餅放在最低部了。然後選擇從最上面的餅和次大的烙餅
,重複上面的步驟。每兩次翻轉,把不再正確位置上的最大餅放在合適的位置上。
那麼,我們一共需要多少次翻轉才能把這些烙餅給翻轉過來呢?
首先,經過兩次翻轉可以把最大的烙餅翻轉到最下面。因此,最多需要把上面的n-1個烙餅依次翻轉兩次。那麼,排到剩下第一個和第二個時, 我們需要2(n-2)次翻轉。最後,第一個和第二個最多需要一次翻轉(可能一次都不需要,已經排好序了)。
四、我們已經求出了一個解法,但是會不會有更好的方法通過更少的翻轉達到相同的目的呢?
考慮一種情況,比如上面的圖1,序號1、2、3的最上面三個烙餅其實是三個相鄰順序已經排好的烙餅。考慮每次翻轉時都讓把兩個本來應該相鄰在烙餅儘可能地換到一起。這樣,當所有的烙餅都換到一起之後,實際上就是完成排序了。(從這個意義上來說,每次翻最大烙餅的方案實質上就是每次把最大的和次大的交換到一起,但是通過了兩次翻轉,不是每一次都儘可能的把兩個本來應該相鄰在烙餅儘可能地換到一起)
那什麼的情況下可以通過最少的翻轉排好序呢?這裡是不是可以選擇一種窮舉的方法,窮舉所有的的每次翻轉的情況,展開來看就是一棵樹,每個結點都有九個子節點(有九中翻轉情況),如果翻轉達到2n-3還未排好序則截斷這一分支。可以根據當前的翻轉次數加上估計還需要的最小翻轉次數(下界值,下限值可以這樣確定:從最後一個位置開始,往前找到第一個與最終結果位置不同的烙餅編號(也就是說排除最後幾個已經就位的烙餅),從該位置到第一個位置,計算相鄰的烙餅的編號不連續的次數,再加上1。)來判斷是否截斷這一分支。我們可以選擇遞迴的方法深度遍曆這顆樹,得到最小的排好序的那些步驟。
五、代碼
public class CFlapjackSorting { private int m_nCake; //烙餅的個數 private int[] m_CakeArray; //烙餅的資訊數組,也就是初始數組的輸入數 private int m_nMaxSwap; //最大的交換次數 private int[] m_SwapArray; //儲存交換位置資訊的數組 private int[] m_ReverseCakeArray; //當時翻轉烙餅資訊數組 private int[] m_ReverseCakeArraySwap; //當時儲存交換位置資訊的數組 private int m_nSearch; //搜尋次數 public CFlapjackSorting() { m_nCake = 0; m_nMaxSwap = 0; } /** * * @param pCakeArray 輸入的烙餅資訊 * @param nCake 數組的長度 */ public void init(int[] pCakeArray, int nCake) { assert (pCakeArray != null); m_nCake = nCake; m_CakeArray = new int[m_nCake]; assert (m_CakeArray != null); //初始化烙餅數組 for (int i = 0; i < m_nCake; i++) { m_CakeArray[i] = pCakeArray[i]; } m_nMaxSwap = upBound(m_nCake); m_SwapArray = new int[m_nMaxSwap + 1]; assert (m_SwapArray != null); m_ReverseCakeArray = new int[m_nCake]; for (int i = 0; i < m_nCake; i++) { m_ReverseCakeArray[i] = m_CakeArray[i]; } m_ReverseCakeArraySwap = new int[m_nMaxSwap+1]; } /** * 最大上界:可以翻轉的最大次數 * @param nCake 烙餅的個數 * @return */ public int upBound(int nCake) { return 2 * nCake; } /** * 最小下界 * @param pCakeArray * @param nCake * @return */ int LowerBound(int[] pCakeArray, int nCake) { int t, lower = 0; for (int i = 1; i < nCake; i++) { t = pCakeArray[i] - pCakeArray[i - 1]; if ((t == 1) || (t == -1)) { } else { lower++; } } return lower; } /** * 搜尋函數 * @param step 第幾步 */ public void search(int step) { int minEstimate; //最小交換次數估計 m_nSearch++; minEstimate = LowerBound(m_ReverseCakeArray, m_nCake); if (step + minEstimate > m_nMaxSwap) return; //判讀是否排好序 if (isSorted(m_ReverseCakeArray, m_nCake)) { //如果排好序了,而且翻轉次數小於最大翻轉次數。否則終止搜尋 if (step < m_nMaxSwap) { m_nMaxSwap = step; for (int i = 0; i < m_nMaxSwap; i++) { m_SwapArray[i] = m_ReverseCakeArraySwap[i]; } } return; } //進行翻轉 for (int i = 1; i < m_nCake; i++) { revert(0, i); m_ReverseCakeArraySwap[step] = i; //記錄每次翻轉時翻轉的位置資訊 search(step + 1); revert(0, i); } } /** * 判斷是否排好序 * @param pCakeArray * @param nCake * @return */ public boolean isSorted(int[] pCakeArray, int nCake) { for (int i = 1; i < nCake; i++) { if (pCakeArray[i] < pCakeArray[i - 1]) { return false; } } return true; } public void revert(int nBegin, int nEnd) { int t; for (int i = nBegin, j = nEnd; i < j; i++, j--) { t = m_ReverseCakeArray[i]; m_ReverseCakeArray[i] = m_ReverseCakeArray[j]; m_ReverseCakeArray[j] = t; } } /** * 輸出翻轉的位置資訊 * 搜尋的次數 * 翻轉的次數 * 翻轉的每一步翻轉情況 */ public void output() { for (int i = 0; i < m_nMaxSwap; i++) { System.out.printf("%d", m_SwapArray[i]); } System.out.printf("\n|Search Times|:%d\n", m_nSearch); System.out.printf("Total swap times = %d\n", m_nMaxSwap); perReverseArrayOutput(m_CakeArray,m_SwapArray,m_nCake,m_nMaxSwap); } /** * 通過記錄每次翻轉位置的數組,輸入每次翻轉的數組 * 顯示每一步的翻轉結果 * @param cakeArray 最初要排序的數組 * @param swapArray 記錄每次翻轉時翻轉的位置 *@param nCake 數組的數量 * @param maxSwap 翻轉的次數 */ public static void perReverseArrayOutput(int[] cakeArray,int[] swapArray,int nCake,int maxSwap){ System.out.printf("%d\n", maxSwap); int t; for(int i=0;i<maxSwap;i++) { System.out.printf("%d ",swapArray[maxSwap]); for (int x = 0,y = swapArray[i]; x < y; x++, y--) { t = cakeArray[x]; cakeArray[x] = cakeArray[y]; cakeArray[y] = t; } for(int k=0;k<nCake;k++){ System.out.printf("%d ",cakeArray[k]); } System.out.print("\n"); } } public void run(int[] pCakeArray,int nCake){ init(pCakeArray,nCake); m_nSearch=0; search(0); }}
六、參考部落格
jinLei_zhao的部落格(一摞烙餅的排序)
Huaerge的部落格(一摞烙餅的排序)
編程之美—烙餅排序問題(JAVA)