生成排列就是研究数1,2,……,n生成所有的排列问题。下面根据一道例题来进行讲解说明这两种算法的思路和区别。
输出全排列(转自PTA)
请编写程序输出前n个正整数的全排列(n<10),并通过9个测试用例(即n从1到9)观察n逐步增大时程序的运行时间。
输入格式:
输入给出正整数n(<10)。
输出格式:
输出1到n的全排列。每种排列占一行,数字间无空格。排列的输出顺序为字典序,即序列a1,a2,⋯,an排在序列b1,b2,⋯,bn之前,如果存在k使得a1=b1,⋯,ak=bk 并且 ak+1<bk+1。
输入样例:
3
输出样例:
123
132
213
231
312
321
题目思路清晰,就是直接输出生成的全排列,按字典序输出。
第一种算法
假定可以生成n-1个数的所有排列,那么就可以扩展方法来生成1,2,……,n这n个数的排列。举个例子:如果能生成数2,3,……,n的所有排列,并且在每个排列的前面加上数1,就可得出以1开头的排列数;接着,生成数1,3,……,n的所有排列,并且在每个排列的前面加上数2,就可得出以2开头的排列数……以此类推。重复这个过程直到最后生成1,2,……,n-1的所有排列,并且在每个排列的前面加上数n。
图解:
假设我选择以1开头的排列,则第二个数就不能为1,只能是2,3;当第二个数选择2时,第三个数只能选3,走完一条路径即生成一个排列,最后输出;接着回退到上一步,第二个数选择3,则第三个数只能选择2,以此类推。类似于回溯法思想,走到路的尽头或者是中途走不下去了就回退到上一步。
AC代码
#include<iostream>
using namespace std;
int visited[10]={0};//0为没被访问,1为已使用
int arr[10];//存储排列输出
void func1(int step, int n)
{
if(step==n+1)
{
for(int i=1;i<=n;i++)
{
printf("%d",arr[i]);
}
printf("\n");
}
else
{
for(int i=1;i<=n;i++)
{
if(visited[i]==0)
{
visited[i]=1;
arr[step]=i;//当前步(当前位置)选择了i
func1(step+1,n);
visited[i]=0;
//关键点,每次访问完都要置零,表示该数还没被使用过,
//以便回溯到上一步的时候使用。
}
}
}
}
int main()
{
int n=0;
scanf("%d",&n);
func1(1,n);
return 0;
}
输出结果:
第二种算法
方法如下:首先把1放到第一个位置,并且在[2, n]的位置生成2,3,……,n这n-1个数的全排列;接着,把1放到第二个位置,并且在第1和[3, n]的位置生成2,3,……,n这n-1个数的全排列……以此类推。直到最后把1放到第n个位置,并且在[1, n-1]的位置生成2,3,……,n这n-1个数的全排列。
实现代码(本题要求按字典序输出,该方法不符合题目要求)
#include<iostream>
using namespace std;
int arr[10];//存储排列输出
void func2(int m, int n)
{
if(m==n+1)
{
for(int i=1;i<=n;i++)
{
printf("%d",arr[i]);
}
printf("\n");
}
else
{
for(int i=1;i<=n;i++)
{
if(arr[i]==0)
{
arr[i]=m;//在第i个位置放置m(不用step,为了防止混淆),和算法1的主要区别
func2(m+1,n);
arr[i]=0;
}
}
}
}
int main()
{
int n=0;
scanf("%d",&n);
func2(1,n);
return 0;
}
输出结果:
在最外层循环里,m=1,即第一次循环生成1在第一个位置的排列,第二次循环生成1在第二个位置的排列……以此类推,这就是第二种算法的主要思想。
总结
第一种算法是1,2,……,n这n个数依次放到第一个位置生成排列,第二种算法是把1(当然也可以是其它的数,例如n)依次放到第1,2,……,n这n个位置生成排列。还不是很理解的同学可以结合两种算法生成的输出结果对比看一下。希望能帮助到大家!