全排列
粗略版,待将来修改。
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:
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdbool.h>
- long a[1000],n;
- bool vis[1000];
- void dfs(long pos)
- {
- 10. long i;
- 11. if (pos==n+1)
- 12. {
- 13. for (i=1;i<=n;i++)
- 14. printf("%ld ",a[i]);
- 15. printf("\n");
- 16. return;
- 17. }
- 18. for (i=1;i<=n;i++)
- 19. if (!vis[i])
- 20. {
- 21. vis[i]=1;
- 22. a[pos]=i;
- 23. dfs(pos+1);
- 24. vis[i]=0;
- 25. }
26. }
- 27.
28. int main()
29. {
- 30. long i;
- 31. scanf("%ld",&n);
- 32. for (i=1;i<=n;i++)
- 33. vis[i]=0;
- 34. dfs(1);
- 35. return 0;
36. }
1-Another1(按顺序):
同Way1,如果添加一个数据结构记录当前可以使用的数值,那么可以节省判断数值是否可以使用的时间。
这里使用c++的set,依然是按顺序排列。但实际上由于要保存set的数值,运算次数更多了。
Code:
- #include <cstdio>
- #include <cstdlib>
- #include <cstring>
- #include <cmath>
- #include <set>
- #include <map>
- #include <list>
- #include <queue>
- #include <stack>
10. #include <vector>
11. #include <algorithm>
12. #include <iostream>
13. using namespace std;
14. const long maxn=1e3+5;
- 15.
16. long a[maxn],n;
17. set<long>st;
- 18.
19. void dfs(long pos)
20. {
- 21. long i;
- 22. set<long>::iterator j,k;
- 23. if (pos==n+1)
- 24. {
- 25. for (i=1;i<=n;i++)
- 26. printf("%ld ",a[i]);
- 27. printf("\n");
- 28. return;
- 29. }
- 30. set<long>st1=st;
- 31. for (j=st1.begin();j!=st1.end();)
- 32. {
- 33. a[pos]=*j;
- 34. k=j;
- 35. j++;
- 36. st.erase(*k);
- 37. dfs(pos+1);
- 38. st.insert(a[pos]);
- 39. }
40. }
- 41.
42. int main()
43. {
- 44. long i;
- 45. scanf("%ld",&n);
- 46. for (i=1;i<=n;i++)
- 47. st.insert(i);
- 48. dfs(1);
- 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:
- #include <stdio.h>
- #include <stdlib.h>
- long a[1000],n;
- void dfs(long j)
- {
- long i;
- if (j==0)
- 10. {
- 11. for (i=n;i>=1;i--) //倒序输出
- 12. printf("%ld ",a[i]);
- 13. printf("\n");
- 14. return;
- 15. }
- 16. long t;
- 17. for (i=1;i<j;i++)
- 18. {
- 19. //swap(a[i],a[j]) 把a[i]放在最后
- 20. t=a[i];
- 21. a[i]=a[j];
- 22. a[j]=t;
- 23.
- 24. dfs(j-1); //可使用的数的数目减1
- 25.
- 26. //swap(a[i],a[j]) 还原a[i]
- 27. t=a[i];
- 28. a[i]=a[j];
- 29. a[j]=t;
- 30. }
- 31. dfs(j-1); //对于采用a[j],不需要进行交换
32. }
- 33.
34. int main()
35. {
- 36. long i;
- 37. scanf("%ld",&n);
- 38. for (i=1;i<=n;i++)
- 39. a[i]=i;
- 40. dfs(n);
- 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:
- //一个数指向下一个可以使用的数,可以跳过查找那些已经使用过的数
- #include <stdio.h>
- #include <stdlib.h>
- long a[1000],n,next[1000],c[1000];
- int main()
- {
- long i,j,k,l,num,g,max_num;
- 10. scanf("%ld",&n);
- 11. c[n]=1; c[n-1]=1;
- 12. for (i=n-2;i>=1;i--)
- 13. c[i]=c[i+1]*(n-i);
- 14. max_num=1;
- 15. for (i=2;i<=n;i++)
- 16. max_num*=i;
- 17. for (i=0;i<max_num;i++)
- 18. {
- 19. for (j=0;j<n;j++)
- 20. next[j]=j+1;
- 21. num=i;
- 22. for (j=1;j<=n;j++)
- 23. {
- 24. g=num/c[j];
- 25. l=0;
- 26. for (k=0;k<g;k++)
- 27. l=next[l];
- 28. printf("%ld ",next[l]);
- 29. next[l]=next[next[l]];
- 30. num=num%c[j];
- 31. }
- 32. printf("\n");
- 33. }
- 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:
- //交换数字时,要合理安排先后顺序,从而减少辅助变量的使用。
- #include <stdio.h>
- #include <stdlib.h>
- long a[1000],n;
- int main()
- {
- long i,j,k,l,t,max_num;
- 10. scanf("%ld",&n);
- 11. max_num=1;
- 12. for (i=2;i<=n;i++)
- 13. max_num*=i;
- 14. for (i=1;i<=n;i++)
- 15. a[i]=i;
- 16. for (i=0;i<max_num;i++)
- 17. {
- 18. for (j=1;j<=n;j++)
- 19. printf("%ld ",a[j]);
- 20. printf("\n");
- 21. for (j=n-1;j>=1;j--)
- 22. if (a[j]<a[j+1])
- 23. break;
- 24. l=j+n+1;
- 25. for (k=j+1;k<=l/2;k++)
- 26. {
- 27. t=a[k];
- 28. a[k]=a[l-k];
- 29. a[l-k]=t;
- 30. }
- 31.
- 32. for (k=j+1;k<=n;k++) //其实用二分更快(如果n数值大)
- 33. if (a[k]>a[j])
- 34. {
- 35. t=a[j];
- 36. a[j]=a[k];
- 37. a[k]=t;
- 38. break;
- 39. }
- 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:
- #include <stdio.h>
- #include <stdlib.h>
- long a[1000],n;
- void dfs(long j)
- {
- long i;
- if (j==n+1)
- 10. {
- 11. for (i=1;i<=n;i++)
- 12. printf("%ld ",a[i]);
- 13. printf("\n");
- 14. return;
- 15. }
- 16. long t;
- 17. dfs(j+1);
- 18. for (i=j+1;i<=n;i++)
- 19. {
- 20. t=a[i];
- 21. a[i]=a[j];
- 22. a[j]=t;
- 23.
- 24. dfs(j+1);
- 25.
- 26. t=a[i];
- 27. a[i]=a[j];
- 28. a[j]=t;
- 29. }
30. }
- 31.
32. int main()
33. {
- 34. long i;
- 35. scanf("%ld",&n);
- 36. for (i=1;i<=n;i++)
- 37. a[i]=i;
- 38. dfs(1);
39. }
各程序运行时间:
使用
让处理、判断蕴含在全排列中
1. 八皇后问题
Problem:
n*n矩阵,放置n个皇后,使皇后之间不在同一个行、列、对角线。
Solution:
每一行,每一列有且仅有一个皇后。
以皇后在第1行,第2行,…,第n行所在的列数构成数列,数列一定是全排列的其中一个数列。
另外还有对角线这个约束条件。
Code:
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdbool.h>
- long a[10],n;
- long vis[10][10];
- /*若一个格子被一个皇后影响(行、列、对角线),则该格子数值加1;当该格子数值大于1时,则不合理*/
- long min(long a,long b)
10. {
- 11. if (a>b)
- 12. return b;
- 13. else
- 14. return a;
15. }
- 16.
17. long max(long a,long b)
18. {
- 19. if (a>b)
- 20. return a;
- 21. else
- 22. return b;
23. }
- 24.
25. void dfs(long x)
26. {
- 27. long i;
- 28. if (x==n+1)
- 29. {
- 30. for (i=1;i<=n;i++)
- 31. printf("%ld ",a[i]);
- 32. printf("\n");
- 33. return;
- 34. }
- 35. long j,t,y;
- 36. for (i=x;i<=n;i++)
- 37. {
- 38. t=a[i];
- 39. a[i]=a[x];
- 40. a[x]=t;
- 41. y=a[x];
- 42.
- 43. if (vis[x][y]==0)
- 44. {
- 45. //列
- 46. for (j=1;j<=n;j++)
- 47. vis[j][y]++;
- 48. //对角线1
- 49. t=n-max(x,y);
- 50. for (j=-min(x,y)+1;j<=t;j++)
- 51. vis[x+j][y+j]++;
- 52. //对角线2
- 53. t=min(n-x,y-1);
- 54. for (j=max(1-x,y-n);j<=t;j++)
- 55. vis[x+j][y-j]++;
- 56. vis[x][y]-=2;
- 57.
- 58. dfs(x+1);
- 59.
- 60. //列
- 61. for (j=1;j<=n;j++)
- 62. vis[j][y]--;
- 63. //对角线1
- 64. t=n-max(x,y);
- 65. for (j=-min(x,y)+1;j<=t;j++)
- 66. vis[x+j][y+j]--;
- 67. //对角线2
- 68. t=min(n-x,y-1);
- 69. for (j=max(1-x,y-n);j<=t;j++)
- 70. vis[x+j][y-j]--;
- 71. vis[x][y]+=2;
- 72. }
- 73.
- 74. t=a[i];
- 75. a[i]=a[x];
- 76. a[x]=t;
- 77. }
78. }
- 79.
80. int main()
81. {
- 82. long i;
- 83. scanf("%ld",&n);
- 84. for (i=1;i<=n;i++)
- 85. a[i]=i;
- 86. dfs(1);
- 87. return 0;
88. }
2. 任务分配
Problem:
n个任务,n个人,每人分配一个任务,任意一个任务有且仅被一个人选取。求最小总代价。
Solution1:
全排列
剪枝:唯有目前的代价小于最小总代价时,才继续搜索
Code1:
- #include <stdio.h>
- #include <stdlib.h>
- #define inf 1e9
- long mincost=inf,n;
- long c[50][50],a[50],result[50];
- //c[j][i]:第i个人分配第i个项目的代价
- void dfs(long j,long cost)
10. {
- 11. long i;
- 12. if (j==n+1)
- 13. {
- 14. if (cost<mincost)
- 15. {
- 16. mincost=cost;
- 17. for (i=1;i<=n;i++) //输出其中一个方案
- 18. result[i]=a[i];
- 19. }
- 20. }
- 21. long t;
- 22. for (i=j;i<=n;i++)
- 23. {
- 24. t=a[i];
- 25. a[i]=a[j];
- 26. a[j]=t;
- 27.
- 28. if (cost+c[j][a[j]]<mincost) //剪枝
- 29. dfs(j+1,cost+c[j][a[j]]);
- 30.
- 31. t=a[i];
- 32. a[i]=a[j];
- 33. a[j]=t;
- 34. }
35. }
- 36.
37. int main()
38. {
- 39. long i,j;
- 40. scanf("%ld",&n);
- 41. for (i=1;i<=n;i++)
- 42. for (j=1;j<=n;j++)
- 43. scanf("%ld",&c[i][j]);
- 44. for (i=1;i<=n;i++)
- 45. a[i]=i;
- 46. dfs(1,0);
- 47. printf("%ld\n",mincost);
- 48. for (i=1;i<=n;i++)
- 49. printf("%ld ",result[i]);
- 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:
- #include <stdio.h>
- #include <stdlib.h>
- #define inf 1e9
- long mincost=inf,n;
- long road[50][50],a[50],result[50];
- //road[i][j]:编号为i的点到编号为j的的代价
- void dfs(long j,long cost)
10. {
- 11. long i;
- 12. if (j==n+1)
- 13. {
- 14. if (cost+road[a[n]][a[1]]<mincost)
- 15. {
- 16. mincost=cost+road[a[n]][a[1]];
- 17. for (i=1;i<=n;i++) //输出其中一个方案
- 18. result[i]=a[i];
- 19. }
- 20. }
- 21. long t;
- 22. for (i=j;i<=n;i++)
- 23. {
- 24. t=a[i];
- 25. a[i]=a[j];
- 26. a[j]=t;
- 27. //road[a[j-1]][a[j]]:第j-1个点到第j个点的的代价
- 28. if (cost+road[a[j-1]][a[j]]<mincost) //剪枝
- 29. dfs(j+1,cost+road[a[j-1]][a[j]]);
- 30.
- 31. t=a[i];
- 32. a[i]=a[j];
- 33. a[j]=t;
- 34. }
35. }
- 36.
37. int main()
38. {
- 39. long i,j;
- 40. scanf("%ld",&n);
- 41. for (i=1;i<=n;i++)
- 42. for (j=1;j<=n;j++)
- 43. scanf("%ld",&road[i][j]);
- 44. a[0]=0;
- 45. for (i=1;i<=n;i++)
- 46. road[0][i]=0;
- 47. for (i=1;i<=n;i++)
- 48. a[i]=i;
- 49. dfs(1,0);
- 50. printf("%ld\n",mincost);
- 51. for (i=1;i<=n;i++)
- 52. printf("%ld ",result[i]);
- 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)具有唯一值,但哈密顿回路不是这样。