从n个不同元素中取出n个元素的排列,称为n个不同元素的全排列。
可以证明,n个元素的全排列的总数是n!。
全排列的生成算法就是对于给定的元素集合,用有效的方法将所有可能的全排列无重复无遗漏地列举出来。
n个不同元素的排列都可以与n个自然数1、2、……,n的排列一一对应,所以,这里就以n个数字的排列为例说明排列的生成算法。全排列的生成算法就是讨论怎样从一个排列生成它一个新的排列的方法。
1.字典序算法
不同的排列,可以从左到右逐个比较对应的元素的顺序来决定他们的先后顺序。设有集合{1、2、……n-1、n}的两个排列 a0a1 ……aj……an-1和 b1b2……bj……bn-1bn,从左端开始,逐个比较它们的对应元素的大小,如果出现对应数字不相同的第一个位置为 j,并且aj<bj,则a0a1 ……aj……an-1就排在b0b1……bj……bn-1的前面。例如对于5个数字的排列12354和12345,两个排列的前三个数字相同,出现不同数字的位置是 j=3,而排列12354的第四个数字5在排列12345的第四个数字4之后,所以排列12345在前,排列12354在后。根据这样的规定,5个数字的所有的排列中,第一个排列是12345,最后一个是54321。这种判断排列顺序的方法称为字典序法。
根据字典序法生成所有全排列的算法如下:
设P是集合{1、2、……n-1、n}的一个全排列:P=p1p2……pj-1pjpj+1……pn(1≤p1、 p2、……、pn≤n-1)
第一步:从排列的右端开始,找出第一个比右边数字小的数字的序号j,即j=max{i|pi<pi+1 ,i>j}
第二步:在pj 的右边的数字中,找出所有比pj 大的数字中最小的数字pk,即k=min{i|pi>pj ,i>j}
第三步:交换pi,pk
第四步:再将排列右端的递减部分pj+1pj+2……pn倒转,就得到了一个新的排列P'=p1p2……pj-1pjpn……kpj+2pj+1。
例如839647521是数字1~9的一个排列。从它生成下一个排列的步骤如下:
自右至左找出排列中第一个比右边数字小的数字4 839647521
在该数字后的数字中找出比4大的数中最小的一个5 839647521
将5与4交换 839657421
将7421倒转 839651247
所以839647521的下一个排列是 839651247。
为了得到集合{1、2、……n-1、n}的全体全全排列,可以从原始排列12……n开始,按照字典序法逐个生成它们的后继排列。当得到的后继排列是最后一个n……21时就结束。
字典序算法的程序代码:
//字典序算法
//输入:排列元素个数n
//输出:n个元素的排列
#include <iostream>
#include <iomanip>
using namespace std;
void dict(int *p,int n);
void output(int *p,int n);
int t;
int main(void)
{
freopen("in.dat","r",stdin);
int n,*p;
while(cin>>n)
{
p=new int[n];
for(int i=0;i<n;i++)
p[i]=i+1;
t=0;
dict(p,n);
}
return 0;
}
void dict(int *p,int n)
{
int i,j;
while(1)
{
output(p,n);
for(i=n-2;i>=0;i--)
if(p[i]<p[i+1])
break;
if(i<0)
break;
for(j=n-1;j>=0;j--)
if(p[j]>p[i])
break;
swap(p[i],p[j]);
j=n-1;
while(i<j)
swap(p[++i],p[j--]);
}
}
void output(int *p,int n)
{
Cout<<setw(5)<<++t<<”:”;
for(int i=0;i<n;i++)
cout<<setw(3)<<p[i];
cout<<endl;
}