• 编程之美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("
     |Search Times| : %d
    ", m_nSearch);
     68         printf("Total Swap times = %d
    ", 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("
    ");
    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("
    ");
    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("
     |Search Times| : %d
    ", m_nSearch);
     87         printf("Total Swap times = %d
    
    ", 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

  • 相关阅读:
    rzc generate exited with code -2147450730.
    c#WebService动态调用
    c#BarTender打印,打印微调
    记一次ios下h5页面图片显示问题
    FID
    RSA密钥对生成,并解析公钥指数和模数
    angularjs-6
    angularjs-5
    angularjs-4
    angularjs-4
  • 原文地址:https://www.cnblogs.com/TenosDoIt/p/3250742.html
Copyright © 2020-2023  润新知