全排列的算法(二)——递增进位法

来源:互联网 发布:数据收集网站 编辑:程序博客网 时间:2024/05/22 12:54

2.递增进位法

集合{12、……n-1n}的全排列中,只有原始排列12……n的各个元素的顺序是自然顺序,其余排列的元素的顺序都要改变。为了说明元素间顺序的改变,引入了排列的逆序的概念,它在行列式理论中起着重要的作用。

设集合{12,……,n}的一个排列是 p0p1……pn-1,如果对于任意的k和l(0kln-1),当k<l 有 pk>pl,则称数对(pk,pl)是排列 p0p1……pn-1 的一个逆序对。在排列 p0p1……pn-1 中,在元素 pj 左面比它大的的元素的个数为 qj( j=0l……,n-1),那么,称 q0,q1,……,qn-1为这个排列的逆序序列,而数 q0+q1+……+qn-1 叫做这个排列的逆序数。

例如,排列48625137中,1前面比它大的数有5个,2前面比它大的数有3个,……,因此,这个排列的的逆序序列是53402110,它的逆序数则是5+3+4+0+2+1+1+0=16

因为在集合{12,…… ,n}中,至多有n-1个整数大于1,同样,至多有n-2个整数大于2,……,而没有其他的数大于n。所以,集合{12,……,n}的任何一个排列的逆序序列 q0,q1,……,qn-1 都满足条件:

0q0≤n-10q1≤n-2,……,0qn-2≤1qn-1=0

于是,集合{12,……,n}的全排列中,逆序数最小的排列是123…… n,它的逆序数是0,而逆序数最大的排列是n(n-1)……321,它的逆序数 n(n-1)/2

在排列与逆序序列之间存在一一对应关系,不同的逆序序列对应着不同的排列,所以我们可以从逆序序列生成对应的排列。下面是根据排列的逆序序列生成排列的算法。

用n个元素的数组p存放排列的元素,用数组q存放排列的逆序序列,当排列的逆序序列已知时,按下列步骤可以生成一个排列:

第一步:将数组 p全部初始化为0

第二步:将1放在左起的第 q[0]个空位处。再将2放在余下空位中左起的第q[1]个空位处,……一般地,将k放在余下的空位中左起的第 q[k-1]个空位处,……,直到将n放入最后一个空位。

执行完上述步骤后,就得到了集合{12,…,n}的一个排列,它的逆序序列是q[0],q[1],……,q[n-1]。

n个数的全排列中第一个排列是123……n它的逆序序列是0,0,……,0,将它看作是一个n位数00……0,给它加1得到的是00……10,对应的逆序序列是00,……,10,根据它生成的排列是前一个排列的后继排列,继续加1,就可以生成全体排列了。应当注意的是,qn-1 总是保持为0,累加从 qn-2 开始,在累加时,当该数其中某一位超出它的表示范围时需要进位,但是注意到逆序序列所要满足的条件,所以进位方法需要采用一种称为递增进位的方法。一般来说,在递增进位法中,因为 0qi<n-i所以,当 qn-i≥i 时就发生进位。例如,开始时,第一个逆序序列是00……00,对它进行累加,也就是将qn-2 加1,因为 qn-2必须小于1,所以当qn-2 再加1qn-2=2 就需要进位。同样,对于 qn-3 来说,当qn-3 =3 时也要进位,……。这就是将它称为递增进位法的原因。

递增进位算法生成全排列的程序代码如下:

//递增进位算法

//输入:排列元素个数n

//输出:n个元素的排列

#include<iostream>

#include <iomanip>

using namespace std;

 

void increase(int *p,int n);           //递增进位排列算法

int invert(int *q,int n);                  //计算逆序序列及逆序数   

void output(int *p,int n);              //输出排列

int total;                                     //排列计数变量   

 

int main()

{

    freopen("in.dat","r",stdin);

// freopen("out.dat","w",stdout);

int n,*p;

    while(cin>>n)

{

p=new int[n];

perm(p,n);

}

    return 0;

}

 

void increase(int *p,int n)

{

int *q=new int[n],i,j,k;

fill_n(q,n,0);

while(1)

{

fill_n(p,n,0);                         //数组初始化

for(i=0;i<n;i++)                    //数组空位计数

{

k=-1;                                  //空位计数变量

for(j=0;j<n;j++)

{

if(p[j]==0)

k++;

if(k==q[i])

{

p[j]=i+1;                           //把数据填入空位 

break;

}

}

}

output(p,n);                         //输出一个排列

if(!invert(q,n))                     //全部排列都已生成,结束

break;

}

}

 

int invert(int *q,int n)                            

{

int i=n-2,m=0;                            //qn-1开始

q[i]++;                                       //累加

while(q[i]==n-i)                         //进位 

{

q[i--]=0;

q[i]++;

}

for(i=0;i<n;i++)                        //计算逆序数

m+=q[i];

return m;

}

 

void output(int *p,int n)

{

cout<<setw(10)<<++total<<": ";

for(int i=0;i<n;i++)

cout<<setw(3)<<p[i];

cout<<endl;

}

在该算法中,从逆序序列 0,0,……,0 开始生成全排列,最后一个排列的逆序序列是 n,……,2,1,0,将它加1(加在右起第二位上),进位结果是 0,0,……,0,回到第一个排列,所以是通过检测逆序数是否为0确定排列是否全部生成的。

0 0
原创粉丝点击