編程之美1.3 一摞烙餅的排序


問題描述:

這是前綴翻轉排序問題,書上的內容這里不詳述,給個電子版下載地址

本文源碼編譯環境:codeblock with gcc

書上給的源碼有點小錯誤,編譯通不過,這里是修改過后的原始代碼:

  1 /****************************************************************/
  2 //
  3 // 烙餅排序實現
  4 //
  5 /****************************************************************/
  6 #include <cstdio>
  7 #include<cassert>
  8 class CPrefixSorting
  9 {
 10 public:
 11 
 12     CPrefixSorting()    
 13     {
 14         m_nCakeCnt = 0;
 15         m_nMaxSwap = 0;
 16         m_CakeArray = NULL;
 17         m_SwapArray = NULL;
 18         m_ReverseCakeArray = NULL;
 19         m_ReverseCakeArraySwap = NULL;
 20     }
 21 
 22     ~CPrefixSorting()
 23     {printf("1");
 24         if( m_CakeArray != NULL )
 25         {
 26             delete []m_CakeArray;  
 27         }
 28         if( m_SwapArray != NULL )
 29         {
 30             delete []m_SwapArray;  
 31         }
 32         if( m_ReverseCakeArray != NULL )
 33         {
 34             delete []m_ReverseCakeArray;  
 35         }
 36         if( m_ReverseCakeArraySwap != NULL )
 37         {
 38             delete []m_ReverseCakeArraySwap;  
 39         }
 40         printf("2");
 41     }
 42 
 43     //
 44     // 計算烙餅翻轉信息
 45     // @param
 46     // pCakeArray    存儲烙餅索引數組
 47     // nCakeCnt    烙餅個數
 48     //
 49     void Run(int* pCakeArray, int nCakeCnt)
 50     {
 51         Init(pCakeArray, nCakeCnt);
 52 
 53         m_nSearch = 0;
 54         Search(0);
 55     }
 56 
 57     //
 58     // 輸出烙餅具體翻轉的次數
 59     //
 60     void Output()
 61     {
 62         for(int i = 0; i < m_nMaxSwap; i++)
 63         {
 64             printf("%d ", m_SwapArray[i]);
 65         }
 66 
 67         printf("\n |Search Times| : %d\n", m_nSearch);
 68         printf("Total Swap times = %d\n", m_nMaxSwap);
 69     }
 70 
 71 private:
 72 
 73     //
 74     // 初始化數組信息
 75     // @param
 76     // pCakeArray    存儲烙餅索引數組
 77     // nCakeCnt    烙餅個數
 78     //
 79     void Init(int* pCakeArray, int nCakeCnt)
 80     {
 81         assert(pCakeArray != NULL);
 82         assert(nCakeCnt > 0);
 83 
 84         m_nCakeCnt = nCakeCnt;
 85 
 86         // 初始化烙餅數組
 87         m_CakeArray = new int[m_nCakeCnt]; 
 88         assert(m_CakeArray != NULL);
 89         for(int i = 0; i < m_nCakeCnt; i++)
 90         {
 91             m_CakeArray[i] = pCakeArray[i];
 92         }
 93 
 94         // 設置最多交換次數信息
 95         m_nMaxSwap = UpBound(m_nCakeCnt);
 96 
 97         // 初始化交換結果數組 
 98         m_SwapArray = new int[m_nMaxSwap + 1];
 99         assert(m_SwapArray != NULL);
100 
101         // 初始化中間交換結果信息
102         m_ReverseCakeArray = new int[m_nCakeCnt];
103         for(int i = 0; i < m_nCakeCnt; i++)
104         {
105             m_ReverseCakeArray[i] = m_CakeArray[i];
106         }
107         m_ReverseCakeArraySwap = new int[m_nMaxSwap + 1];
108     }
109 
110 
111     //
112     // 尋找當前翻轉的上界
113     //
114     //
115     int UpBound(int nCakeCnt)
116     {
117         return nCakeCnt*2;
118     }
119 
120     //
121     // 尋找當前翻轉的下界
122     //
123     //
124     int LowerBound(int* pCakeArray, int nCakeCnt)
125     {
126         int t, ret = 0;
127 
128         // 根據當前數組的排序信息情況來判斷最少需要交換多少次
129         for(int i = 1; i < nCakeCnt; i++)
130         {
131             // 判斷位置相鄰的兩個烙餅,是否為尺寸排序上相鄰的
132             t = pCakeArray[i] - pCakeArray[i-1];
133             if((t == 1) || (t == -1))
134             {
135             } 
136             else
137             {
138                 ret++;
139             }
140         }
141         return ret;
142     }
143 
144     // 排序的主函數
145     void Search(int step)
146     {
147         int i, nEstimate;
148 
149         m_nSearch++;
150 
151         // 估算這次搜索所需要的最小交換次數
152         nEstimate = LowerBound(m_ReverseCakeArray, m_nCakeCnt);
153         if(step + nEstimate > m_nMaxSwap)
154             return;
155 
156         // 如果已經排好序,即翻轉完成,輸出結果
157         if(IsSorted(m_ReverseCakeArray, m_nCakeCnt))
158         {
159             if(step < m_nMaxSwap)
160             { 
161                 m_nMaxSwap = step;
162                 for(i = 0; i < m_nMaxSwap; i++)
163                     m_SwapArray[i] = m_ReverseCakeArraySwap[i];
164             }
165             return;
166         }
167 
168         // 遞歸進行翻轉
169         for(i = 1; i < m_nCakeCnt; i++)
170         {
171             Revert(0, i);
172             m_ReverseCakeArraySwap[step] = i;
173             Search(step + 1);
174             Revert(0, i);
175         }
176     }
177     //
178     // true : 已經排好序
179     // false : 未排序
180     //
181     bool IsSorted(int* pCakeArray, int nCakeCnt)
182     {
183         for(int i = 1; i < nCakeCnt; i++)
184         {
185             if(pCakeArray[i-1] > pCakeArray[i])
186             {
187                 return false;
188             }
189         }
190         return true;
191     }
192 
193     //
194     // 翻轉烙餅信息
195     //    
196     void Revert(int nBegin, int nEnd)
197     {
198         assert(nEnd > nBegin);
199         int i, j, t;
200 
201         // 翻轉烙餅信息
202         for(i = nBegin, j = nEnd; i < j; i++, j--)
203         {
204             t = m_ReverseCakeArray[i]; 
205             m_ReverseCakeArray[i] = m_ReverseCakeArray[j];
206             m_ReverseCakeArray[j] = t;
207         }
208     }
209 
210 private:
211 
212     int* m_CakeArray;    // 烙餅信息數組
213     int m_nCakeCnt;    // 烙餅個數
214     int m_nMaxSwap;    // 最多交換次數。根據前面的推斷,這里最多為
215     // m_nCakeCnt * 2 
216     int* m_SwapArray;    // 交換結果數組
217 
218     int* m_ReverseCakeArray;    // 當前翻轉烙餅信息數組
219     int* m_ReverseCakeArraySwap;    // 當前翻轉烙餅交換結果數組
220     int m_nSearch;    // 當前搜索次數信息
221 };
222 
223 
224 int main()
225 {
226     CPrefixSorting a;
227     int cakeArry[] = {3,2,1,6,5,4,9,8,7,0};
228     a.Run(cakeArry, sizeof(cakeArry) / sizeof(int));
229     a.Output();
230     printf("\n");
231     CPrefixSorting b;
232     int cakeArry1[] = {12,3,2,1,11,6,5,10,4,9,8,7,0};
233     b.Run(cakeArry1, sizeof(cakeArry1) / sizeof(int));
234     b.Output();
235     printf("\n");
236     CPrefixSorting c;
237     int cakeArry2[] = {2,0,1};
238     c.Run(cakeArry2, sizeof(cakeArry2) / sizeof(int));
239     c.Output();
240     return 0;
241 }
原始代碼

三個測試樣例運行結果:

下面的討論主要是針對書上搜索算法的改進,並且在原始代碼的基礎上進行修改。下面是對原始程序的幾點說明:

1、要盡快找到最小翻轉次數,就要減少搜索過程中(深搜)搜索子樹的數目,即剪枝。原文中是通過分析某個序列,使該序列有序的最小翻轉次數和最大翻轉次數來剪枝的。關於翻轉次數的上界和下界,原文中也提到了分別是 (5n+5)/3向上取整 和 15n/14向上取整,需要注意的是這里的上界和下界是指:任意給定一個序列,最大翻轉次數是(5n+5)/3向上取整 ,最小翻轉次數是15n/14向上取整;而程序中的上界和下界分別是針對某個序列而言,比如對於序列5 4 3 2 1,按照公式下界是5*15/14 = 6,但其實只要翻轉1次。

2、原始程序中使用2n作為上界,其實按照每次把最大的燒餅翻轉的最底下的算法,上界應該為2(n-1),具體書上已經有說明(當然一開始我們也可以選擇(5n+5)/3向上取整作為上界)。然后在搜索的過程中每找到一個比當前上界更好的方案,就更新上界。

3、對於下界書上做法是:因為每一次翻轉最多只能使一個烙餅與大小和它相鄰的烙餅排到一起,所以如果當前n個烙餅中,有m對相鄰的烙餅大小不相鄰,那么我們至少需要m次翻轉才能排好序。


下面是改進的幾個地方,改進后的代碼以及改進結果見本文末尾:

1、翻轉上界的改進:其實我們可以通過選擇某個方案對原始序列進行翻轉使之有序,計算翻轉次數,那么這個翻轉次數可以作為上界,因為最優的方案肯定不會比當前的方案差。改進的代碼里選擇書上講的翻轉方案:把最大的未就位的烙餅翻轉到它對應的位置,具體見函數UpBound_case

2、翻轉下屆的改進:不僅要像書上那樣考慮相鄰位置烙餅大小是否相鄰,還要考慮最大燒餅是否就位,若序列中最大燒餅不在最后的位置必然要反轉一次使其就位,而這次是最大燒餅就位的翻轉不改變序列的相鄰燒餅的連續性,具體見LowerBound函數。

3、search中是否超過上屆的條件判斷:具體說明見search函數的注釋(下面代碼224行 和 240行)

4、在搜索的過程中,對於某個序列,原始程序是每個位置都翻轉一次,改進后,對於序列后面已經排好序的位置就不進行翻轉,因為對排好序的位置翻轉必然不會減少翻轉次數,程序中通過search函數第二個參數實現。

5、在對序列翻轉再搜索時,源程序是從第一個位置依次到最后一個位置翻轉再搜索,改進后,我們對翻轉每個位置翻轉后的序列進行一個評估,評估它離有序序列的距離,然后優先搜索距離小的序列。評估函數其實就是LowerBound函數

6、對序列翻轉再搜索時,若某個翻轉剛好使序列有序,那么其他翻轉方案肯定會導致無序,因此可以放棄搜索,代碼通過search的返回值實現,具體見下面代碼292行

改進代碼和結果如下:

  1 #include<cstdio>
  2 #include<cassert>
  3 #include<vector>
  4 #include<algorithm>
  5 struct node
  6 {
  7     int index;
  8     int score;
  9 };
 10 
 11 bool comp(struct node a, struct node b)
 12 {
 13     return a.score < b.score;
 14 }
 15 
 16 class CPrefixSorting
 17 {
 18 public:
 19 
 20     CPrefixSorting()
 21     {
 22         m_nCakeCnt = 0;
 23         m_nMaxSwap = 0;
 24         m_CakeArray = NULL;
 25         m_SwapArray = NULL;
 26         m_ReverseCakeArray = NULL;
 27         m_ReverseCakeArraySwap = NULL;
 28         flag1 = false;
 29     }
 30 
 31     ~CPrefixSorting()
 32     {
 33         releaseAll();
 34     }
 35 
 36     void releaseAll()
 37     {
 38         if( m_CakeArray != NULL )
 39         {
 40             delete []m_CakeArray;
 41             m_CakeArray = NULL;
 42         }
 43         if( m_SwapArray != NULL )
 44         {
 45             delete []m_SwapArray;
 46             m_SwapArray = NULL;
 47         }
 48         if( m_ReverseCakeArray != NULL )
 49         {
 50             delete []m_ReverseCakeArray;
 51             m_ReverseCakeArray = NULL;
 52         }
 53         if( m_ReverseCakeArraySwap != NULL )
 54         {
 55             delete []m_ReverseCakeArraySwap;
 56             m_ReverseCakeArraySwap = NULL;
 57         }
 58     }
 59 
 60     //
 61     // 計算烙餅翻轉信息
 62     // @param
 63     // pCakeArray    存儲烙餅索引數組
 64     // nCakeCnt    烙餅個數
 65     //
 66     void Run(int* pCakeArray, int nCakeCnt)
 67     {
 68 
 69         releaseAll();
 70         Init(pCakeArray, nCakeCnt);
 71 
 72         m_nSearch = 0;
 73         Search(0, nCakeCnt - 1);
 74     }
 75 
 76     //
 77     // 輸出烙餅具體翻轉的次數
 78     //
 79     void Output()
 80     {
 81         for(int i = 0; i < m_nMaxSwap; i++)
 82         {
 83             printf("%d ", m_SwapArray[i]);
 84         }
 85 
 86         printf("\n |Search Times| : %d\n", m_nSearch);
 87         printf("Total Swap times = %d\n\n", m_nMaxSwap);
 88     }
 89 
 90 private:
 91 
 92     //
 93     // 初始化數組信息
 94     // @param
 95     // pCakeArray    存儲烙餅索引數組
 96     // nCakeCnt    烙餅個數
 97     //
 98     void Init(int* pCakeArray, int nCakeCnt)
 99     {
100         assert(pCakeArray != NULL);
101         assert(nCakeCnt > 0);
102 
103         flag1 = false;
104 
105         m_nCakeCnt = nCakeCnt;
106 
107         // 初始化烙餅數組
108         m_CakeArray = new int[m_nCakeCnt];
109         assert(m_CakeArray != NULL);
110         for(int i = 0; i < m_nCakeCnt; i++)
111         {
112             m_CakeArray[i] = pCakeArray[i];
113         }
114 
115         // 初始化中間交換結果信息
116         m_ReverseCakeArray = new int[m_nCakeCnt];
117         for(int i = 0; i < m_nCakeCnt; i++)
118         {
119             m_ReverseCakeArray[i] = m_CakeArray[i];
120         }
121 
122         // 設置最多交換次數信息
123         //m_nMaxSwap = UpBound(m_nCakeCnt);
124         m_nMaxSwap = UpBound_case(m_ReverseCakeArray, m_nCakeCnt);
125         //UpBound_case中m_ReverseCakeArray改變了
126         //m_ReverseCakeArray復原
127         for(int i = 0; i < m_nCakeCnt; i++)
128         {
129             m_ReverseCakeArray[i] = m_CakeArray[i];
130         }
131 
132         // 初始化交換結果數組
133         m_SwapArray = new int[m_nMaxSwap + 1];
134         assert(m_SwapArray != NULL);
135 
136         m_ReverseCakeArraySwap = new int[m_nMaxSwap];
137         assert(m_ReverseCakeArraySwap != NULL);
138     }
139 
140 
141     //
142     // 尋找當前翻轉的上界
143     //
144     //
145     int UpBound(int nCakeCnt)
146     {
147         return nCakeCnt*2-2;
148     }
149 
150     //--每次把最大的翻轉到最下面,計算這種方法所需要的次數
151     //--計算過程中保存翻轉結果,因為這種方法可能就是最小翻轉次數的方法
152     //--這個次數可以作為翻轉次數的上界
153     int UpBound_case(int* pCakeArray, int nCakeCnt)
154     {
155         int re = 0;
156         for (int j = nCakeCnt - 1 ; ;)
157         {
158             while(j > 0 && j == pCakeArray[j])
159                 --j;
160             if (j <= 0)
161                 break;
162             int i = j;
163             while (i >= 0 && pCakeArray[i] != j)
164                 --i;
165             if (i != 0)
166             {
167                 Revert(pCakeArray, 0, i);
168                 re++;
169             }
170             Revert(pCakeArray, 0, j);
171             re++;
172             --j;
173         }
174         return re;
175     }
176 
177     //
178     // 尋找當前翻轉的下界
179     //
180     //
181     int LowerBound(int* pCakeArray, int nCakeCnt)
182     {
183         int t, ret = 0;
184 
185         // 根據當前數組的排序信息情況來判斷最少需要交換多少次
186         for(int i = 1; i < nCakeCnt; i++)
187         {
188             // 判斷位置相鄰的兩個烙餅,是否為尺寸排序上相鄰的
189             t = pCakeArray[i] - pCakeArray[i-1];
190             if((t == 1) || (t == -1))
191             {
192             }
193             else
194             {
195                 ret++;
196             }
197         }
198         //--如果最大的烙餅不在最后一個位置,則要多翻轉一次
199         if (pCakeArray[nCakeCnt-1] != nCakeCnt-1) ret++;
200         return ret;
201     }
202 
203     //--序列評估函數,對一個序列,若到達有序狀態所需的翻轉次數越少,得分越少
204     //--若到達有序狀態所需的翻轉次數越多,得分越多
205     //--搜索時可以優先搜索得分少的序列,這樣能盡快達到最優解
206     int Evaluate(int* pCakeArray, int nCakeCnt)
207     {
208         return LowerBound(pCakeArray, nCakeCnt);
209     }
210 
211     // 排序的主函數
212     //--第endBound+1 個燒餅后均以排好序,沒有必要對排好序的進行交換
213     //--因為交換已經排好序的燒餅只能使交換次數增大
214     //--如果當前搜索的序列排好序則返回true,否則返回false
215     bool Search(int step, int endBound)
216     {
217         int i, nEstimate;
218 
219         m_nSearch++;
220 
221         // 估算這次搜索所需要的最小交換次數
222         nEstimate = LowerBound(m_ReverseCakeArray, m_nCakeCnt);
223 
224         //--遇到相等情形,若對於翻轉次數為m_nMaxSwap的結果已經保存,流程繼續。
225         //--后面再遇到就可以跳過了,因為針對該翻轉次數的結果已經保存,
226         //無需再計算
227         if(step + nEstimate == m_nMaxSwap && flag1 == false);
228         else if(step + nEstimate >= m_nMaxSwap)
229             return false;
230 
231         //重新計算排好序的位置
232         int k = endBound;
233         while(k > 0 && k == m_ReverseCakeArray[k])
234             --k;
235 
236         // 如果k=0,說明已經排好序,即翻轉完成,輸出結果
237         //if(IsSorted(m_ReverseCakeArray, m_nCakeCnt))
238         if(k == 0)
239         {
240             if(step < m_nMaxSwap)
241             {
242                 //--當前找到的一個解
243                 m_nMaxSwap = step;
244                 for(i = 0; i < m_nMaxSwap; i++)
245                     m_SwapArray[i] = m_ReverseCakeArraySwap[i];
246             }
247             else if(step == m_nMaxSwap && flag1 == false)
248             {
249                 //--只有第一次碰到step == m_nMaxSwap時才做如下操作
250                 //--因為m_nMaxSwap可能是最小翻轉次數,因此要記錄此次結果
251                 //--后面再碰到相等時,可以忽略,因為不用重復保存結果
252                 for(i = 0; i < m_nMaxSwap; i++)
253                     m_SwapArray[i] = m_ReverseCakeArraySwap[i];
254                 flag1 = true;
255             }
256             return true;
257         }
258 
259         // 遞歸進行翻轉,k之后已經排好序的位置就不用翻轉了
260         std::vector<node> swapIndexScore;
261         //對翻轉后的序列進行評估,評估它到排序好的序列之間的距離,優先搜索距離小的序列
262         for(i = 1; i <=k; i++)
263         {
264             struct node tnode;
265             tnode.index = i;
266             tnode.score = nEstimate;//原始序列的分數
267             //求翻轉后的分數,翻轉后只有翻轉位置影響分數的大小
268             if(i != m_nCakeCnt - 1)
269             {
270                 if(abs(m_ReverseCakeArray[i] - m_ReverseCakeArray[i+1]) == 1)
271                     tnode.score++;
272                 if(abs(m_ReverseCakeArray[0] - m_ReverseCakeArray[i+1]) == 1)
273                     tnode.score--;
274             }
275             else
276             {
277                 if(m_ReverseCakeArray[i] == i)tnode.score++;
278                 if(m_ReverseCakeArray[0] == i)tnode.score--;
279             }
280             swapIndexScore.push_back(tnode);
281         }
282         //按照得分小到大排序,得分小的優先搜索
283         sort(swapIndexScore.begin(), swapIndexScore.end(),comp);
284 
285         for(i = 0; i < swapIndexScore.size() ; i++)
286         {
287             Revert(m_ReverseCakeArray, 0, swapIndexScore[i].index);
288             m_ReverseCakeArraySwap[step] = swapIndexScore[i].index;
289             bool isDone = Search(step + 1, k);
290             Revert(m_ReverseCakeArray, 0, swapIndexScore[i].index);
291             //如果該搜索序列有序,那么其他翻轉方案肯定會導致無序,因此不需要搜索
292             if(isDone == true)break;
293         }
294 
295 //        for(i = 1; i <=k ; i++)
296 //        {
297 //            Revert(m_ReverseCakeArray, 0, i);
298 //            m_ReverseCakeArraySwap[step] = i;
299 //            Search(step + 1, k);
300 //            Revert(m_ReverseCakeArray, 0, i);
301 //        }
302         return false;
303     }
304     //
305     // true : 已經排好序
306     // false : 未排序
307     //
308     bool IsSorted(int* pCakeArray, int nCakeCnt)
309     {
310         for(int i = 1; i < nCakeCnt; i++)
311         {
312             if(pCakeArray[i-1] > pCakeArray[i])
313             {
314                 return false;
315             }
316         }
317         return true;
318     }
319 
320     //
321     // 翻轉數組
322     //
323     void Revert(int arry[], int nBegin, int nEnd)
324     {
325         assert(nEnd > nBegin);
326         int i, j, t;
327 
328         // 翻轉數組
329         for(i = nBegin, j = nEnd; i < j; i++, j--)
330         {
331             t = arry[i];
332             arry[i] = arry[j];
333             arry[j] = t;
334         }
335     }
336 
337 private:
338 
339     int* m_CakeArray;    // 烙餅信息數組
340     int m_nCakeCnt;    // 烙餅個數
341     int m_nMaxSwap;    // 最多交換次數。根據前面的推斷,這里最多為
342     // m_nCakeCnt * 2
343     int* m_SwapArray;    // 交換結果數組
344 
345     int* m_ReverseCakeArray;    // 當前翻轉烙餅信息數組
346     int* m_ReverseCakeArraySwap;    // 當前翻轉烙餅交換結果數組
347     int m_nSearch;    // 當前搜索次數信息
348     bool flag1;//--當最開始計算的m_nMaxSwap是最小翻轉次數時,若記錄了這個翻轉的
349                //結果,flag1 = ture,否則為false,詳見search函數
350 };
351 
352 int main()
353 {
354     CPrefixSorting a;
355     int cakeArry[] = {3,2,1,6,5,4,9,8,7,0};
356     int cakeArry1[] = {12,3,2,1,11,6,5,10,4,9,8,7,0};
357     int cakeArry2[] = {2,0,1};
358     a.Run(cakeArry, sizeof(cakeArry) / sizeof(int));
359     a.Output();
360     a.Run(cakeArry1, sizeof(cakeArry1) / sizeof(int));
361     a.Output();
362     a.Run(cakeArry2, sizeof(cakeArry2) / sizeof(int));
363     a.Output();
364     return 0;
365 }

 

【版權聲明】轉載請注明出處 http://www.cnblogs.com/TenosDoIt/p/3250742.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM