数学上的全排列问题:
给定m个数,可以排列成n位数的所有情况;
例:3 个数 ( 1,2,3 ) 排列成两位数[ 含有重复数字 ]有:
11,12 ,13,21,22,23,31,32,33;
例:2个数( 1,2 ) 排列成三位数:
111, 112, 121, 122, 211, 212, 221, 222;
由上易找到规律:
对于 n 位 m 个要求的数
[ 不妨设为1,2,3的 2位数 ],我们只需从最高位开始,依次冠以第一个数( 11 );
之后保持高位不变,最低位游历所有要排列的数:11, 12, 13 ;
最低位游历过之后,次低位变为第二个数继续游历:21, 22, 23 ;
知道最高位也游历过所有要排列的数为止;
这样的问题通过for循环也是可以实现的,但是对于n位数,需要用n个for循环来游历所有位的数据,
代码长度可想而知;
所以我们想到了用递归的方法来解决;
问题分析:
1.
因为我们不可能一次将排列的数按整形来输出,所以要把它的每一位存入数组中输出;
需要定义两个数组 ( arr[ M ]和a[ N ] ) 分别代表了要排列的数和第 i 位[ i从0开始 ]的数;
2.
当 i 小于 n 的时候( 即没有达到n位数的要求的时候 ),游历第 i 位;
当 i 大于等于 n 的时候( 已经满足要求 ) 按 i=0 到 i=n-1( 第一位到最后一位 )输出;
实现代码( 含重复数字 ):
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> const int M=10; int n,m,a[M],arr[M]={1 , 2 ,3 ,4}; void dfs(int v) { if(v >= n) { for(int i = 0;i<n;i++) printf("%d ",a[i]); printf("\n"); return ; } for(int i = 0; i<m;i++) { a[v] = arr[i]; dfs(v+1); } } int main() { while(scanf("%d%d",&n,&m)==2) { dfs(0); } return 0; }
代码分析:
1.
dfs(0); 表示:从第一个( 第0个 )数开始排列 ;
2.
arr[ M ]={ 1,2,3,4 } 表示:要实现全排列的数有M个分别是 1, 2, 3, 4;
3.
当 i 小于 n 的时候( 即没有达到n位数的要求的时候 ),游历第 i 位;
for(int i = 0; i<m;i++) { a[v] = arr[i]; dfs(v+1); }
4.
当 i 大于等于 n 的时候( 已经满足要求 ) 按 i=0 到 i=n-1( 第一位到最后一位 )输出;
if(v >= n) { for(int i = 0;i<n;i++) printf("%d ",a[i]); printf("\n"); return ; }
以上的代码是实现含有重复数字的全排列,下面我们来看看不含有重复数字的全排列 :
与含有重复数字的算法基本相同,唯一的不同在于 :
不含有重复数字的全排列算法需要引入标记数组( mark[M] );
注意, 当 m<n 的时候( 不重复排列的数小于位数 ),这种情况下是错误的,结果不予输出;
实现代码( 含重复数字 ):
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> const int M=10; int n,m,a[M],arr[M]={1,2,3,4},mark[M]; void dfs(int v) { if(v >= n) { for(int i = 0;i<n;i++) printf("%d ",a[i]); printf("\n"); return ; } for(int i = 0; i<m;i++) { if(!mark[i]) { mark[i]=1; a[v] = arr[i]; dfs(v+1); mark[i]=0; } } } int main() { while(scanf("%d%d",&n,&m)==2) { memset(mark,0,sizeof(mark)); dfs(0); } return 0; }
1.
利用回溯法:当 i 小于 n 的时候( 即没有达到n位数的要求的时候 ),游历第 i 位,并添加标记mark[i]=1;
当mark[ i ]==1 的时候,说明已经使用过了,进行下一个数的排列;
递归完毕后,返回,并将改为重新标记为 0;
for(int i = 0; i<m;i++) { if(!mark[i]) { mark[i]=1; a[v] = arr[i]; dfs(v+1); mark[i]=0; } }
2.
当 i 大于等于 n 的时候( 已经满足要求 ) 按 i=0 到 i=n-1( 第一位到最后一位 )输出;
if(v >= n) { for(int i = 0;i<n;i++) printf("%d ",a[i]); printf("\n"); return ; }
既然已经知道了,重复和不重复的全排列方法了,现在还有一个问题,就是 :
如果不想输出由某个数字开头( 第i位 )的数字怎么办?( 可以在递归之前排除掉一些情况,节省时间 )
例:3 个数 ( 1,2,3 ) 排列成两位数[ 含有重复数字 ][ 不输出由一开头的数字 ]:
21,22,23,31,32,33;
例:3 个数 ( 1,2,3 ) 排列成两位数[ 不含重复数字 ][ 不输出由一开头的数字 ]:
21,23,31,32;
实际上只需在刚开始加入数组的时候判断开头( 第 i 位 )是否为 k 即可;
①、实现代码如下[ 含有重复数字 ][ 不输出由一开头的数字 ]:
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> const int M=10; int n,m,a[M],arr[M]={1 , 2 ,3 ,4}; void dfs(int v) { if(v >= n) { for(int i = 0;i<n;i++) printf("%d ",a[i]); printf("\n"); return ; } for(int i = 0; i<m;i++) { a[v] = arr[i]; if(a[0]==1) continue; dfs(v+1); } } int main() { while(scanf("%d%d",&n,&m)==2) { dfs(0); } return 0; }
代码分析:
仅仅加入了判断;
for(int i = 0; i<m;i++) { a[v] = arr[i]; if(a[0]==1) continue; dfs(v+1); }
②、实现代码如下[ 不含重复数字 ][ 不输出由一开头的数字 ]:
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> const int M=10; int n,m,a[M],arr[M]={1,2,3,4},mark[M]; void dfs(int v) { if(v >= n) { for(int i = 0;i<n;i++) printf("%d ",a[i]); printf("\n"); return ; } for(int i = 0; i<m;i++) { if(!mark[i]) { mark[i]=1; a[v] = arr[i]; if(a[0]==1) { mark[i]=0; continue; } dfs(v+1); mark[i]=0; } } } int main() { while(scanf("%d%d",&n,&m)==2) { memset(mark,0,sizeof(mark)); dfs(0); } return 0; }
代码分析:
在回溯法中加入了判断,需要在跳出之前,将标记取消( 否则所有含一的都不会输出 );
for(int i = 0; i<m;i++) { if(!mark[i]) { mark[i]=1; a[v] = arr[i]; if(a[0]==1) { mark[i]=0; continue; } dfs(v+1); mark[i]=0; } }
现在基本上可以对全排列随意操作了吧。 ≥~~≤;
版权声明:本文为博主原创文章,未经博主允许不得转载。