全排列的算法(八)——序数法

全排列的生成算法(八)——序数法

 

n个元素的全排列有n!个,如果将排列按顺序编号,并能够按照某种方法建立起每一个序号与一个排列之间的对应关系,那么就可以根据序号确定排列,反过来也可以根据排列确定它的序号。根据排列的序号生成对应排列的方法就称为序数法。

通常,我们使用的计数法是十进制数。十进制数的位权是10,也就是逢十进一。另外还有二进制、八进制和十六进制等,它们的位权分别是2816

排列数与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的右面没有比它小的元素,第四位是34的右面有3个元素小于它。显然,这个排列是4 2 1 3

序数法生成全排列的算法如下:

第一步:将排列的序号m转换成阶乘进制数

第二步:根据阶乘进制数的各位数值将元素12、……、n赋给数组p的相应元素。

在第一步中,从十进制数转换成阶乘进制数,步骤与一般十进制装换成其他进制数相同,也就是用欲转换的数制的位权除十进制数取余数。

第二步中则与递增进位法相同,将数字nn-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位,那么就有0q[i]i-1。这样,从q[0]=q[1]=……出发,对q[n-2]累加1,并按照q[i-1]=i时进位,就可以数次得到序数01、……、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;

}

 

点赞