問題描述:

這是前綴翻轉排序問題,書上的內容這里不詳述,給個電子版下載地址
本文源碼編譯環境: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
