编程之美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