数学上的全排列问题:
给定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;
}
}
现在基本上可以对全排列随意操作了吧。 ≥~~≤;