全排列

 

粗略版,待将来修改。

https://pan.baidu.com/s/1PYw_ghBe43ry82YtOcKZ7w

 

全排列

目录

全排列性质... 1

全排列求法... 2

Way1(按顺序):... 2

1-Another1(按顺序):... 4

1-Another2(不按顺序):... 6

Way2(按顺序):... 8

Way3(按顺序):... 10

Way4(不按顺序):... 12

各程序运行时间:... 13

使用... 13

 

 

全排列性质

如n=3:

123

132

213

231

312

321

对于每个数列,数字1,2,…,n有且仅出现一次。

对于第一个位置,有n种选择,

对于第二个位置,不能与第一个位置重复,有(n-1)种选择,

…,

对于最后一个位置,有1种选择。

所有共有n!个数列。

 

全排列求法

1. 字典序顺序从小到大或从大到小

2. 没有顺序要求

 

Way1(按顺序):

每次确认一个位置的数值,这个数值是以前的位置从未出现的数值。

该方法由于要从1到n找到符合要求的数值,所以时间复杂度特别高,特别是在处理到数列后期的时候,如173254x,那么x从1找到5都不符合条件,耗费特别多的时间。

 

时间复杂度:对于第i位,共有n*(n-1)*…*(n+2-i)种情况能到达第i位,而在第i位,处理的次数都为n次(1~n)。所以总的处理次数:

n + n*n + n*(n-1)*n + … + n*(n-1)*…*2*n = n*(P(n,0)+P(n,1)+…+P(n,n-1)) = n*n!*(1+1/2+1/6+1/24+…) < n!*n*2

 

因为2>1+1/2+1/4+1/8…>1+1/2+1/6+1/24+…。实际上,和约为1.8。

 

设T(n)=P(n,0)+P(n,1)+…+P(n,n-1),有T(n)=T(n-1)*n+1。

 

P.S.:对于频繁调用函数的方法(保存中间状态,栈压入压出),耗时大,空间消耗也大。

 

Code:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <stdbool.h>  
  4.   
  5. long a[1000],n;  
  6. bool vis[1000];  
  7.   
  8. void dfs(long pos)  
  9. {  
  10. 10.     long i;  
  11. 11.     if (pos==n+1)  
  12. 12.     {  
  13. 13.         for (i=1;i<=n;i++)  
  14. 14.             printf("%ld ",a[i]);  
  15. 15.         printf("\n");  
  16. 16.         return;  
  17. 17.     }  
  18. 18.     for (i=1;i<=n;i++)  
  19. 19.         if (!vis[i])  
  20. 20.         {  
  21. 21.             vis[i]=1;  
  22. 22.             a[pos]=i;  
  23. 23.             dfs(pos+1);  
  24. 24.             vis[i]=0;  
  25. 25.         }  

26. }  

  1. 27.   

28. int main()  

29. {  

  1. 30.     long i;  
  2. 31.     scanf("%ld",&n);  
  3. 32.     for (i=1;i<=n;i++)  
  4. 33.         vis[i]=0;  
  5. 34.     dfs(1);  
  6. 35.     return 0;  

36. }  

 

1-Another1(按顺序):

同Way1,如果添加一个数据结构记录当前可以使用的数值,那么可以节省判断数值是否可以使用的时间。

这里使用c++的set,依然是按顺序排列。但实际上由于要保存set的数值,运算次数更多了。

 

Code:

  1. #include <cstdio>  
  2. #include <cstdlib>  
  3. #include <cstring>  
  4. #include <cmath>  
  5. #include <set>  
  6. #include <map>  
  7. #include <list>  
  8. #include <queue>  
  9. #include <stack>  

10. #include <vector>  

11. #include <algorithm>  

12. #include <iostream>  

13. using namespace std;  

14. const long maxn=1e3+5;  

  1. 15.   

16. long a[maxn],n;  

17. set<long>st;  

  1. 18.   

19. void dfs(long pos)  

20. {  

  1. 21.     long i;  
  2. 22.     set<long>::iterator j,k;  
  3. 23.     if (pos==n+1)  
  4. 24.     {  
  5. 25.         for (i=1;i<=n;i++)  
  6. 26.             printf("%ld ",a[i]);  
  7. 27.         printf("\n");  
  8. 28.         return;  
  9. 29.     }  
  10. 30.     set<long>st1=st;  
  11. 31.     for (j=st1.begin();j!=st1.end();)  
  12. 32.     {  
  13. 33.         a[pos]=*j;  
  14. 34.         k=j;  
  15. 35.         j++;  
  16. 36.         st.erase(*k);  
  17. 37.         dfs(pos+1);  
  18. 38.         st.insert(a[pos]);  
  19. 39.     }  

40. }  

  1. 41.   

42. int main()  

43. {  

  1. 44.     long i;  
  2. 45.     scanf("%ld",&n);  
  3. 46.     for (i=1;i<=n;i++)  
  4. 47.         st.insert(i);  
  5. 48.     dfs(1);  
  6. 49.     return 0;  

50. }  

 

1-Another2(不按顺序):

同1-Another1,记录当前可以使用的数值。这里的处理方法是:对于使用过的数,往后放(通过两数交换),使未使用过的数都放在最前面,详见程序。

 

时间复杂度:对于“void dfs(long j) ” (j=n,n-1,…,1),执行(j-1)次交换操作。对于第j位,共有n*(n-1)*…*(n+2-j)种情况能到达第i位。所以总的交换次数:

n-1 + n*(n-2) + n*(n-1)*(n-3) + … + n*(n-1)*…*3*1 = ( n+n*(n-1)+…+n*(n-1)*…*2 ) - ( 1+n+n*(n-1)+…+n*(n-1)*…*(n-3) ) = n!*(1+1/2+1/6+1/24+…) - n!*(1/2+1/6+…) = n!

原则上比方法1快不少

 

Code:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.   
  4. long a[1000],n;  
  5.   
  6. void dfs(long j)  
  7. {  
  8.     long i;  
  9.     if (j==0)  
  10. 10.     {  
  11. 11.         for (i=n;i>=1;i--)  //倒序输出  
  12. 12.             printf("%ld ",a[i]);  
  13. 13.         printf("\n");  
  14. 14.         return;  
  15. 15.     }  
  16. 16.     long t;  
  17. 17.     for (i=1;i<j;i++)  
  18. 18.     {  
  19. 19.         //swap(a[i],a[j]) 把a[i]放在最后  
  20. 20.         t=a[i];  
  21. 21.         a[i]=a[j];  
  22. 22.         a[j]=t;  
  23. 23.   
  24. 24.         dfs(j-1);   //可使用的数的数目减1  
  25. 25.   
  26. 26.         //swap(a[i],a[j]) 还原a[i]  
  27. 27.         t=a[i];  
  28. 28.         a[i]=a[j];  
  29. 29.         a[j]=t;  
  30. 30.     }  
  31. 31.     dfs(j-1);   //对于采用a[j],不需要进行交换  

32. }  

  1. 33.   

34. int main()  

35. {  

  1. 36.     long i;  
  2. 37.     scanf("%ld",&n);  
  3. 38.     for (i=1;i<=n;i++)  
  4. 39.         a[i]=i;  
  5. 40.     dfs(n);  
  6. 41.     return 0;  

42. }  

 

 

Way2(按顺序):

1~n的全排列若按字典序从小到大排列,

对于排在第num位的数列(num=0~n-1) {a[1],a[2],…,a[n]},有:

num=b[1]*(n-1)!+b[2]*(n-2)!+…+b[n-1]*1!

其中b[k]为从1到a[k]-1(a[k]为第k个数),未出现在a[1]~a[k-1]中的数字数目,如152634,则b[4]=2,因为从1到5(6-1),数字3,4未出现在a[1],a[2],a[3]中。

 

证明:

对于b[k]*(n-k)!,代表的是以a[1],a[2],…,a[k-1]为首的数列集合中,a[1],a[2],…,a[k-1],a[k],…在其中的首位置。如15423,152xx的个数为2!=2个,153xx的个数为2!=2个,以15为首的数列中,154xx在其中的首位置为2!*2=4。

 

任意一个b[1],b[2],…,b[n]对应唯一的a[1],a[2],…,a[n],

而任意一个a[1],a[2],…,a[n]对应唯一的b[1],b[2],…,b[n]。

 

任意一个b[1],b[2],…,b[n]对应唯一的num,

而任意一个num对应唯一的b[1],b[2],…,b[n]。

 

时间复杂度:对于b[k],找到对应的a[k]就需要进行b[k]次寻找后继操作(见下文代码)。而b[k]的值为0,1,…,n-k的可能性是相同的,所以对于b[k],平均操作次数为(n-k)/2,总的寻找后继操作为:n!*( (n-1)/2+(n-2)/2+…+1/2 )

= n!*(n-1)*n/4。

 

Code:

  1. //一个数指向下一个可以使用的数,可以跳过查找那些已经使用过的数  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4.   
  5. long a[1000],n,next[1000],c[1000];  
  6.   
  7. int main()  
  8. {  
  9.     long i,j,k,l,num,g,max_num;  
  10. 10.     scanf("%ld",&n);  
  11. 11.     c[n]=1; c[n-1]=1;  
  12. 12.     for (i=n-2;i>=1;i--)  
  13. 13.         c[i]=c[i+1]*(n-i);  
  14. 14.     max_num=1;  
  15. 15.     for (i=2;i<=n;i++)  
  16. 16.         max_num*=i;  
  17. 17.     for (i=0;i<max_num;i++)  
  18. 18.     {  
  19. 19.         for (j=0;j<n;j++)  
  20. 20.             next[j]=j+1;  
  21. 21.         num=i;  
  22. 22.         for (j=1;j<=n;j++)  
  23. 23.         {  
  24. 24.             g=num/c[j];  
  25. 25.             l=0;  
  26. 26.             for (k=0;k<g;k++)  
  27. 27.                 l=next[l];  
  28. 28.             printf("%ld ",next[l]);  
  29. 29.             next[l]=next[next[l]];  
  30. 30.             num=num%c[j];  
  31. 31.         }  
  32. 32.         printf("\n");  
  33. 33.     }  
  34. 34.     return 0;  

35. }  

 

 

Way3(按顺序):

字典序从小到大排序,根据第num个数列生成第num+1个数列

如426531->431256,

方法:

1. 从右到左,找到第一个满足数值比 与之右相邻的数 大的数,这个数设为a[k]。如3>1,5>3,6>5,2<6,所以2满足条件。

2. 找到在a[k+1]~a[n]中比a[k]大的最小的数a[l],用于代替a[k];如数字3大于数字2,且是6,5,3,1中比2大的数中数字最小的,所以数字3代替数字2。

3. a[k+1]~a[n]按照从小到大的顺序排列。如剩下的数从小到大排列为1,2,5,6,分别放置于第3,4,5,6位。

 

时间复杂度:若从右到左有k个大于关系,直到遇到最小关系。第2,3步的时间复杂度约为k,而第1步的时间复杂度约为k。总时间复杂度:

O(n+n*(n-1)*2+n*(n-1)*(n-2)*3+)

 

Code:

  1. //交换数字时,要合理安排先后顺序,从而减少辅助变量的使用。  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4.   
  5. long a[1000],n;  
  6.   
  7. int main()  
  8. {  
  9.     long i,j,k,l,t,max_num;  
  10. 10.     scanf("%ld",&n);  
  11. 11.     max_num=1;  
  12. 12.     for (i=2;i<=n;i++)  
  13. 13.         max_num*=i;  
  14. 14.     for (i=1;i<=n;i++)  
  15. 15.         a[i]=i;  
  16. 16.     for (i=0;i<max_num;i++)  
  17. 17.     {  
  18. 18.         for (j=1;j<=n;j++)  
  19. 19.             printf("%ld ",a[j]);  
  20. 20.         printf("\n");  
  21. 21.         for (j=n-1;j>=1;j--)  
  22. 22.             if (a[j]<a[j+1])  
  23. 23.                 break;  
  24. 24.         l=j+n+1;  
  25. 25.         for (k=j+1;k<=l/2;k++)  
  26. 26.         {  
  27. 27.             t=a[k];  
  28. 28.             a[k]=a[l-k];  
  29. 29.             a[l-k]=t;  
  30. 30.         }  
  31. 31.   
  32. 32.         for (k=j+1;k<=n;k++)    //其实用二分更快(如果n数值大)  
  33. 33.             if (a[k]>a[j])  
  34. 34.             {  
  35. 35.                 t=a[j];  
  36. 36.                 a[j]=a[k];  
  37. 37.                 a[k]=t;  
  38. 38.                 break;  
  39. 39.             }  
  40. 40.     }  

41. }  

 

 

 

Way4(不按顺序):

处理到第k位时,让之前未使用过的数都有机会在第k位上,详见程序。代码与1-Another2很像。

 

证明:

若之前的前k位是正确的,证明前k+1位也是正确的。

对于任意一个前k+1位,必定是由它的前k位的前缀生成而成的,而这个前缀有且只有一位,而且在这个前缀下,根据a[k+1]与a[r+1]的交换,必定可以生成这个前k+1位。

 

时间复杂度:O(n),如1-Another2。

 

Code:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.   
  4. long a[1000],n;  
  5.   
  6. void dfs(long j)  
  7. {  
  8.     long i;  
  9.     if (j==n+1)  
  10. 10.     {  
  11. 11.         for (i=1;i<=n;i++)  
  12. 12.             printf("%ld ",a[i]);  
  13. 13.         printf("\n");  
  14. 14.         return;  
  15. 15.     }  
  16. 16.     long t;  
  17. 17.     dfs(j+1);  
  18. 18.     for (i=j+1;i<=n;i++)  
  19. 19.     {  
  20. 20.         t=a[i];  
  21. 21.         a[i]=a[j];  
  22. 22.         a[j]=t;  
  23. 23.   
  24. 24.         dfs(j+1);  
  25. 25.   
  26. 26.         t=a[i];  
  27. 27.         a[i]=a[j];  
  28. 28.         a[j]=t;  
  29. 29.     }  

30. }  

  1. 31.   

32. int main()  

33. {  

  1. 34.     long i;  
  2. 35.     scanf("%ld",&n);  
  3. 36.     for (i=1;i<=n;i++)  
  4. 37.         a[i]=i;  
  5. 38.     dfs(1);  

39. }  

 

各程序运行时间:

 

使用

让处理、判断蕴含在全排列中

 

1. 八皇后问题

Problem:

n*n矩阵,放置n个皇后,使皇后之间不在同一个行、列、对角线。

 

Solution:

每一行,每一列有且仅有一个皇后。

以皇后在第1行,第2行,…,第n行所在的列数构成数列,数列一定是全排列的其中一个数列。

另外还有对角线这个约束条件。

 

Code:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <stdbool.h>  
  4.   
  5. long a[10],n;  
  6. long vis[10][10];  
  7. /*若一个格子被一个皇后影响(行、列、对角线),则该格子数值加1;当该格子数值大于1时,则不合理*/  
  8.   
  9. long min(long a,long b)  

10. {  

  1. 11.     if (a>b)  
  2. 12.         return b;  
  3. 13.     else  
  4. 14.         return a;  

15. }  

  1. 16.   

17. long max(long a,long b)  

18. {  

  1. 19.     if (a>b)  
  2. 20.         return a;  
  3. 21.     else  
  4. 22.         return b;  

23. }  

  1. 24.   

25. void dfs(long x)  

26. {  

  1. 27.     long i;  
  2. 28.     if (x==n+1)  
  3. 29.     {  
  4. 30.         for (i=1;i<=n;i++)  
  5. 31.             printf("%ld ",a[i]);  
  6. 32.         printf("\n");  
  7. 33.         return;  
  8. 34.     }  
  9. 35.     long j,t,y;  
  10. 36.     for (i=x;i<=n;i++)  
  11. 37.         {  
  12. 38.             t=a[i];  
  13. 39.             a[i]=a[x];  
  14. 40.             a[x]=t;  
  15. 41.             y=a[x];  
  16. 42.   
  17. 43.             if (vis[x][y]==0)  
  18. 44.             {  
  19. 45.                 //列  
  20. 46.                 for (j=1;j<=n;j++)  
  21. 47.                     vis[j][y]++;  
  22. 48.                 //对角线1  
  23. 49.                 t=n-max(x,y);  
  24. 50.                 for (j=-min(x,y)+1;j<=t;j++)  
  25. 51.                     vis[x+j][y+j]++;  
  26. 52.                 //对角线2  
  27. 53.                 t=min(n-x,y-1);  
  28. 54.                 for (j=max(1-x,y-n);j<=t;j++)  
  29. 55.                     vis[x+j][y-j]++;  
  30. 56.                 vis[x][y]-=2;  
  31. 57.   
  32. 58.                 dfs(x+1);  
  33. 59.   
  34. 60.                 //列  
  35. 61.                 for (j=1;j<=n;j++)  
  36. 62.                     vis[j][y]--;  
  37. 63.                 //对角线1  
  38. 64.                 t=n-max(x,y);  
  39. 65.                 for (j=-min(x,y)+1;j<=t;j++)  
  40. 66.                     vis[x+j][y+j]--;  
  41. 67.                 //对角线2  
  42. 68.                 t=min(n-x,y-1);  
  43. 69.                 for (j=max(1-x,y-n);j<=t;j++)  
  44. 70.                     vis[x+j][y-j]--;  
  45. 71.                 vis[x][y]+=2;  
  46. 72.             }  
  47. 73.   
  48. 74.             t=a[i];  
  49. 75.             a[i]=a[x];  
  50. 76.             a[x]=t;  
  51. 77.         }  

78. }  

  1. 79.   

80. int main()  

81. {  

  1. 82.     long i;  
  2. 83.     scanf("%ld",&n);  
  3. 84.     for (i=1;i<=n;i++)  
  4. 85.         a[i]=i;  
  5. 86.     dfs(1);  
  6. 87.     return 0;  

88. }  

 

 

2. 任务分配

Problem:

n个任务,n个人,每人分配一个任务,任意一个任务有且仅被一个人选取。求最小总代价。

 

Solution1:

全排列

剪枝:唯有目前的代价小于最小总代价时,才继续搜索

 

Code1:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #define inf 1e9  
  4.   
  5. long mincost=inf,n;  
  6. long c[50][50],a[50],result[50];  
  7. //c[j][i]:第i个人分配第i个项目的代价  
  8.   
  9. void dfs(long j,long cost)  

10. {  

  1. 11.     long i;  
  2. 12.     if (j==n+1)  
  3. 13.     {  
  4. 14.         if (cost<mincost)  
  5. 15.         {  
  6. 16.             mincost=cost;  
  7. 17.             for (i=1;i<=n;i++)   //输出其中一个方案  
  8. 18.                 result[i]=a[i];  
  9. 19.         }  
  10. 20.     }  
  11. 21.     long t;  
  12. 22.     for (i=j;i<=n;i++)  
  13. 23.     {  
  14. 24.         t=a[i];  
  15. 25.         a[i]=a[j];  
  16. 26.         a[j]=t;  
  17. 27.   
  18. 28.         if (cost+c[j][a[j]]<mincost) //剪枝  
  19. 29.             dfs(j+1,cost+c[j][a[j]]);  
  20. 30.   
  21. 31.         t=a[i];  
  22. 32.         a[i]=a[j];  
  23. 33.         a[j]=t;  
  24. 34.     }  

35. }  

  1. 36.   

37. int main()  

38. {  

  1. 39.     long i,j;  
  2. 40.     scanf("%ld",&n);  
  3. 41.     for (i=1;i<=n;i++)  
  4. 42.         for (j=1;j<=n;j++)  
  5. 43.             scanf("%ld",&c[i][j]);  
  6. 44.     for (i=1;i<=n;i++)  
  7. 45.         a[i]=i;  
  8. 46.     dfs(1,0);  
  9. 47.     printf("%ld\n",mincost);  
  10. 48.     for (i=1;i<=n;i++)  
  11. 49.         printf("%ld ",result[i]);  
  12. 50.     return 0;  

51. }  

52. /* 

53. 4 

54. 9 2 7 8 

55. 6 4 3 7 

56. 5 8 1 8 

57. 7 6 9 4 

58. */  

 

 

3. 哈密顿回路

 

Solution1:

同任务分配,求全排列,加上连接起点和终点的边。

 

Code1:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #define inf 1e9  
  4.   
  5. long mincost=inf,n;  
  6. long road[50][50],a[50],result[50];  
  7. //road[i][j]:编号为i的点到编号为j的的代价  
  8.   
  9. void dfs(long j,long cost)  

10. {  

  1. 11.     long i;  
  2. 12.     if (j==n+1)  
  3. 13.     {  
  4. 14.         if (cost+road[a[n]][a[1]]<mincost)  
  5. 15.         {  
  6. 16.             mincost=cost+road[a[n]][a[1]];  
  7. 17.             for (i=1;i<=n;i++)   //输出其中一个方案  
  8. 18.                 result[i]=a[i];  
  9. 19.         }  
  10. 20.     }  
  11. 21.     long t;  
  12. 22.     for (i=j;i<=n;i++)  
  13. 23.     {  
  14. 24.         t=a[i];  
  15. 25.         a[i]=a[j];  
  16. 26.         a[j]=t;  
  17. 27.         //road[a[j-1]][a[j]]:第j-1个点到第j个点的的代价  
  18. 28.         if (cost+road[a[j-1]][a[j]]<mincost) //剪枝  
  19. 29.             dfs(j+1,cost+road[a[j-1]][a[j]]);  
  20. 30.   
  21. 31.         t=a[i];  
  22. 32.         a[i]=a[j];  
  23. 33.         a[j]=t;  
  24. 34.     }  

35. }  

  1. 36.   

37. int main()  

38. {  

  1. 39.     long i,j;  
  2. 40.     scanf("%ld",&n);  
  3. 41.     for (i=1;i<=n;i++)  
  4. 42.         for (j=1;j<=n;j++)  
  5. 43.             scanf("%ld",&road[i][j]);  
  6. 44.     a[0]=0;  
  7. 45.     for (i=1;i<=n;i++)  
  8. 46.         road[0][i]=0;  
  9. 47.     for (i=1;i<=n;i++)  
  10. 48.         a[i]=i;  
  11. 49.     dfs(1,0);  
  12. 50.     printf("%ld\n",mincost);  
  13. 51.     for (i=1;i<=n;i++)  
  14. 52.         printf("%ld ",result[i]);  
  15. 53.     return 0;  

54. }  

55. /* 

56. 4 

57. 9 2 7 8 

58. 6 4 3 7 

59. 5 8 1 8 

60. 7 6 9 4 

61. */  

 

 

Solution2:

与任务分配不同,哈密顿回路的第i个点与第i+1个点有关联,影响着哈密顿回路的数值。

任务分配:f(x)       x为已经分配的任务集合

哈密顿回路:f(x,y)   x为已经经过的点集合,y为目前所在的点

 

任务分配的f(x)具有唯一值,但哈密顿回路不是这样。

 

posted @ 2018-08-31 17:53  congmingyige  阅读(521)  评论(0编辑  收藏  举报