全排列的生成算法(八)——序数法
n个元素的全排列有n!个,如果将排列按顺序编号,并能够按照某种方法建立起每一个序号与一个排列之间的对应关系,那么就可以根据序号确定排列,反过来也可以根据排列确定它的序号。根据排列的序号生成对应排列的方法就称为序数法。
通常,我们使用的计数法是十进制数。十进制数的位权是10,也就是逢十进一。另外还有二进制、八进制和十六进制等,它们的位权分别是2、8和16。
排列数与n!的阶乘密切相关,因此可以用一种阶乘进制数来建立排列与它的序号的对应关系。阶乘进制数用0!、1!、2!、……分别作为(从右向左的)第一位、第二位、……的位权,显然这是一种可变的位权。举例来说,一个多位数123,如果是十进制,它的大小是3×100+2×101+1×102=(123)10,就是一百二十三。如果是八进制,就是(123)8=3×80+2×81+1×82=(83)10,就是十进制的83。如果是阶乘进制,它就是3×0!+2×1!+1×2!=(8)10,即十进制的8。
某一个n阶排列的序号是m,那么将m转换为阶乘进制数后,阶乘进制数的第i位就是在i右面比i小的元素个数。例如4阶排列中(从0开始计数的)第19个排列的序号是19,将19转换成阶乘进制数是3010,那么,第一位是0,表明1的右面没有比1小的元素,而第二位是1,则2的右面有一个元素小于2,第三位是0,即3的右面没有比它小的元素,第四位是3,4的右面有3个元素小于它。显然,这个排列是4 2 1 3。
序数法生成全排列的算法如下:
第一步:将排列的序号m转换成阶乘进制数
第二步:根据阶乘进制数的各位数值将元素1、2、……、n赋给数组p的相应元素。
在第一步中,从十进制数转换成阶乘进制数,步骤与一般十进制装换成其他进制数相同,也就是用欲转换的数制的位权除十进制数取余数。
第二步中则与递增进位法相同,将数字n、n-1、……、1填入n个空格的相应位置。
具体算法如下:
//全排列的序数法
//输入:排列元素个数n
//输出:全体排列
#include <iostream>
#include <iomanip>
using namespace std;
Void perm(int);
Void output(int *,int);
int total;
int main(void)
{
freopen(“in.dat”,”r”,stdin);
int n;
while(cin>>n)
{
total=0;
perm(n);
}
return 0;
}
void output(int *p,int n)
{
cout<<setw(4)<<++total<<“: “;
for(int i=0;i<n;i++)
cout<<setw(3)<<p[i];
cout<<endl;
}
void perm(int n)
{
int i,j,k,m,s;
int *p=new int[n],*q=new int[n];
fill_n(q,n,0); //初始化
m=0; //原始排列序号
while(1)
{
fill_n(p,n,0); //数组初始化
for(i=0;i<n;i++) //按阶乘进制数各位的值安排各元素位置
for(j=n-1,k=0;j>=0;j–) //从n开始
{
if(p[j])
continue;
if(k==q[i])
{
p[j]=n-i;
break;
}
k++;
}
output(p,n); //输出一个排列
s=++m; //下一个排列的序号
for(i=1;i<=n;i++) //将序号转换成阶乘进制数
{
q[n-i]=s%i;
s/=i;
}
for(j=0,k=0;j<n;j++) //如果阶乘进制数各位回到0
k+=q[j];
if(k==0) //结束
break;
}
}
阶乘的序数转换成阶乘进制数后,也可以按照的增进位方式产生后继(阶乘进制)序数。
现在设置数组q,并用q[i]表示阶乘进制数的第n-i-1位,那么就有0≤q[i]≤i-1。这样,从q[0]=q[1]=……出发,对q[n-2]累加1,并按照q[i-1]=i时进位,就可以数次得到序数0、1、……、n!。同时也可以生成全体n阶排列。
//递增进位序数法
//输入:排列元素个数
//输出:全体n阶全排列
#include <iostream>
#include <iomanip>
using namespace std;
void perm(int);
void output(int *,int);
int total;
int main(void)
{
freopen(“in.dat”,”r”,stdin);
int n;
while(cin>>n)
{
total=0;
perm(n);
}
return 0;
}
void output(int *p,int n)
{
cout<<setw(5)<<++total<<“: “;
for(int i=0;i<n;i++)
cout<<setw(3)<<p[i];
cout<<endl;
}
void perm(int n)
{
int *p=new int[n];
int *q=new int[n]; //阶乘进制数
fill_n(q,n,0); //初始化
int i,j,k;
while(1)
{
fill_n(p,n,0); //初始化
for(i=0;i<n;i++) //按照阶乘进制数各位安排元素位置
for(j=n-1,k=0;j>=0;j–)
{
if(p[j])
continue;
if(k==q[i])
{
p[j]=n-i;
break;
}
k++;
}
output(p,n); //输出一个排列
i=n-2; //计算下一个序数
q[i]++;
while(q[i]>n-i-1 && i>0) //递增进位
q[i–]=0,q[i]++;
if(q[0]==n) //超出n位
break; //结束
}
}
int main(void)
{
int n=5;
total=0;
perm(n);
return 0;
}