高效生成所有的排列

这方面的一般算法是递归,可是递归的效率略坑。另外一个经典的算法是next  permutation,即从右边开始找到第一个非增序(从右到左)的最短序列,然后将这个序列最左端的数调换为递增序中第一个比他大的数,然后将右端排序为降序(从右到左),这样就得到了比当前排列刚刚好大一点的新的排列。如此往复就可以生成所有的排列,初始的是1234...n,最后是n....4321。每次生成的过程中都需要扫描尾端得到最短序列,然后二分查找第一个比他大的数,然后替换,然后剩下的重排序(其实重排序只需要将替换之后的排列逆序就可以了)。可以看出每次生成都需要交换大于等于两个位置的数,平摊下来代价仍然很大。下文所说的生成全排列的算法,每次生成一个新的排列都只需要交换两个相邻的数,因此效率非常高。

首先,我们给出一些相关的定义。

每个生成的排列,对于这个排列中的每一个数,我们都有一个标记,代表它可以移动的方向。<号代表向左移动,>号代表向右移动。我们把这些箭头直接标在每个数字的上方,初始状态的1234...n,而且每一个数的方向都是向左。如果某个数比他箭头所指方向的相邻的数大,则我们把这个数作为可以活动的。如果箭头所指方向没有数或者比当前数小,则这个数则是不活动的。然后我们重复如下过程,每次找出最大的可活动的数,按照箭头指示将他与箭头所指相邻方向的数交换,如果当前活动的数不是n,则将不小于当前数的所有数的方向取反。如此往复,直到无法找到可以活动的数为止。

下图中代表的就是小规模下的生成过程,n分别等于2和3.

下面来证明该方法的确生成了所有的排列,对此我们采取归纳法。

首先我们可以看出对于1,2,3的时候都是成立的,其实对于1成立已经足够了。

假设对于n成立,考虑n+1的情况。

刚开始的时候我们移动n+1这个数,当这个数无法移动的时候,他在最左边,已经移动了n+1个位置。此时,这个数无法移动,然后我们开始启动小于n+1的数的移动,而这个移动方案刚好与生成n个数的排列的第一部移动方案一模一样。当移动n这个数之后,我们需要重新移动n+1这个数,因为这个数的方向已经反向了,最后移动到了最右端,共n+1个位置。然后又启动了生成n个数的全排列的第二部移动方案,如此往复。因此对于这个方案在n的时候生成的每个排列,在n+1的时候都有相对应的n+1个排列,只是n+1这个数的位置不同。由n的时候生成了n!个不同的排列,因此n+1的时候也生成了(n+1)!个不同的排列。因此,这个方案的确生成了所有的n的排列。

下面来讨论效率问题,我们每次要交换一个数的时候,都需要找到当前可移动的最大的数,如果用朴素的方法去找这个数的话,我们需要用O(n)的时间去找这个数,总共的时间花费为n*n!。事实上这个是有规律可循的,我们先看下简单情况:当n=2的时候活动序列是2;当n=3时,活动序列为33233;当n=4的时候,活动序列为44434443444244434443444。

我们开一个次数数组A[n],初始时A[k]=k-1;每次我们从这个数组中从右到左寻找第一个A[i]!=0的i,这个数就是每次我们需要移动的数。移动完之后将A[i]减去1,并把A[i+1],A[i+2]...A[n]恢复到他们的初始值。如此往复,直到找不到这个i。然后整个流程结束,由此我们得出对于i这个数,我们把这个数当作移动的数共有(i-1)*(i-1)!次,也即是i!-(i-1)!次,而每次为了得到i这个数,我们需要从右向左寻找n-i+1次,所以总共我们寻找了2*(1!+2!+....n!)次,比上述的n*n!次减少了很多。这里我们另外开一个位置数组,b[n],其中b[i]的值代表的是i这个数在当前排列中所在的位置,有了这个数组我们就好移动数了。因此每次交换两个相邻位置的数的时候,我们在交换上所花的时间为常数:交换相邻的两个数时间为3,交换相邻两个数的方向时间为3,交换B[n]这个位置索引时间也是3。因此最终的时间花费约为11*n!。

具体代码如下。

  1 #include <stdio.h>
  2 #include <malloc.h>
  3 #define MAX 6
  4 int for_out[MAX];//生成的排列数组
  5 int arrow[MAX];//方向数组
  6 int index[MAX];//次数数组
  7 int position[MAX];//位置数组
  8 int sum=0;
  9 
 10 void out_put()//每次都输出一个排列 
 11 {
 12     int for_j;
 13     char for_k;
 14     for(for_j=0;for_j<MAX;for_j++)
 15     {
 16         for_k=*(arrow+*(for_out+for_j))?'>':'<';
 17         printf("%c ",for_k);
 18     }
 19     printf("\n");
 20     for(for_j=0;for_j<MAX;for_j++)
 21     {
 22         printf("%d ",*(for_out+for_j));
 23     }
 24     printf("\n");
 25 }
 26 
 27 void swap(int cursor)
 28 {
 29     int direction,for_j,temp;
 30     direction=arrow[cursor]?1:-1;
 31     direction=position[cursor]+direction;
 32     for_j=for_out[direction];
 33     for_out[direction]=cursor;
 34     for_out[position[cursor]]=for_j;
 35     temp=position[cursor];
 36     position[cursor]=position[for_j];
 37     position[for_j]=temp;
 38 }
 39 
 40 
 41 int main()
 42 {
 43     
 44     int for_i,cursor;
 45     for(for_i=0;for_i<MAX;for_i++)//初始化方向
 46     { 
 47         arrow[for_i]=0;
 48     }
 49     for(for_i=0;for_i<MAX;for_i++)//初始化输出
 50     {
 51         for_out[for_i]=for_i;
 52         index[for_i]=for_i;
 53         position[for_i]=for_i;
 54     }
 55     cursor=MAX-1;
 56     while(1)
 57     {
 58         if(index[cursor]>0)
 59         {
 60             if(cursor==MAX-1)
 61             {
 62                 swap(cursor);
 63                 index[cursor]--;
 64                 out_put();
 65             }
 66             else
 67             {
 68                 swap(cursor);
 69                 for(for_i=cursor;for_i<MAX;for_i++)
 70                 {
 71                     arrow[for_i]=1-arrow[for_i];
 72                 }
 73                 out_put();
 74             }
 75         }
 76         else
 77         {
 78             while(cursor>0&&index[cursor]==0)
 79             {
 80                 cursor--;
 81             }
 82             if(cursor==0)
 83             {
 84                 out_put();
 85                 printf("%d\n",sum);
 86                 return 1;
 87             }
 88             else
 89             {
 90                 swap(cursor);
 91                 index[cursor]--;
 92                 for(for_i=cursor+1;for_i<MAX;for_i++)
 93                 {
 94                     index[for_i]=for_i;
 95                     arrow[for_i]=1-arrow[for_i];
 96                 }
 97                 cursor=MAX-1;
 98                 out_put();
 99             }
100         }
101     }
102 }
103 
104 
105 
106     

 

 

posted @ 2013-11-28 22:26  huangnima  阅读(1354)  评论(0编辑  收藏  举报