• 高效生成所有的排列


    这方面的一般算法是递归,可是递归的效率略坑。另外一个经典的算法是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("
    ");
     20     for(for_j=0;for_j<MAX;for_j++)
     21     {
     22         printf("%d ",*(for_out+for_j));
     23     }
     24     printf("
    ");
     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
    ",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     
  • 相关阅读:
    WIN32窗口框架代码,完善了菜单和子窗口,纯API,可自由扩展,C语言也可以写桌面程序
    自制WINDOWS窗口框架(修改完善后实现了输入和显示功能),C+WIN-API,再也不用面对黑框框学C语言了
    定义一个判断素数的函数
    定义一元二次方程求根函数
    定义一个二维数组反置函数
    让C语言告别简陋的黑框框,WIN32窗口显示九九乘法表(纯C代码)
    C语言练习题40——将一个数组逆序输出
    c语言练习39——向数列中插入一个数
    c语言练习38——求3*3矩阵对角线之和
    js之好看的鼠标点击-光标特效
  • 原文地址:https://www.cnblogs.com/huangfeidian/p/3448604.html
Copyright © 2020-2023  润新知