您的位置:首页 > 其它

全排列生成算法(三)

2011-12-15 16:00 253 查看
基于阶乘数的全排列生成算法,是另一种通过序列顺序,输出全排列的算法。所谓阶乘数,实际上和我们常用的2进制,8进制,10进制,16进制一样,是一种数值的表示形式,所不同的是,上面这几种进制数,相邻位之间的进制是固定值,以10进制为例,第n位与第n+1位之间的进制是10,而阶乘数,相邻两位之间的进制是变值,第n位与第n+1位之间的进制是(n+1)!。对于10进制数,每一位的取值范围也是固定的0~9,而阶乘数每一位的取值范围为0~n。可以证明,任何一个数量,都可以由一个阶乘数唯一表示。下面以23为例,说明其在各种进制中的表现形式

 2进制8进制10进制16进制阶乘数
23101112723173210
其中10进制23所代表的数量的计算方法为

D(23) = 2×10^1 + 3×10^0 = 2×10 + 3×1 = 23

阶乘数3210所代表的数量的计算方法为

F(3210) = 3×3! + 2×2! + 1×1! + 0×0! = 3×6 + 2×2 + 1×1 + 1×0 = 23

对于阶乘数而言,由于阶乘的增长速度非常快,所以其可以表示的数值的范围随着位数的增长十分迅速,对于n位的阶乘数而言,其表示的范围从0~(n+1)!-1,总共(n+1)!个数。阶乘数有很多性质这里我们只介绍其和全排列相关的一些性质。

首先是加法操作,与普通十进制数的加法基本一样,所不同的是对于第n位F
(最低位从第0位开始),如果F
+1>n,那么我们需要将F
置0,同时令F[n+1]+1,如果对于第n+1位,也导致进位,则向高位依次执行进位操作。这里我们看一下F(3210)+1,对于第0位,有F[0]+1=0+1=1>0,所以F[0]=0(实际上阶乘数的第0位一直是0),F[1]+1=1+1=2>1,F[1]=0,……,依次执行,各位都发生进位,最终结果F(3210)+1=F(10000)。

其次,对于n位的阶乘数,每一个阶乘数的各位的数值,正好对应了一个n排列各位的逆序关系。这里以abcd为例。例如F(2110),其对应的排列的意思是,对于排列的第一个元素,其后有两个元素比他小;第二个元素,后面有一个元素比他小;第三个元素,后面有一个元素比他小。最终根据F(2110)构建的排列为cbda。4位的阶乘数,与4排列的对应关系如下表所示。

0000abcd1000bacd2000cabd3000dabc
0010abdc1010badc2010cadb3010dacb
0100acbd1100bcad2100cbad3100dbac
0110acdb1110bcda2110cbda3110dbca
0200adbc1200bdac2200cdab3200dcab
0210adcb1210bdca2210cdba3210dcba
由此,我们就可以利用阶乘数与排列的对应关系构建集合的全排列,算法如下。

对于n个元素的全排列,首先生成n位的阶乘数F[0...n-1],并令F[0...n-1]=0。
每次对F[0...n-1]执行+1操作,所得结果,根据其与排列的逆序对应关系,生成排列。
直到到达F[0...n-1]所能表示的最大数量n!-1为止,全部n!个排列生成完毕。
这里有一个问题需要解决,就是如何根据阶乘数,及其与排列逆序的对应关系生成对应的排列,这里给出一个方法,

以字典序最小的排列a[0...n-1]作为起始,令i从0到n-2。
如果F[i]=0,递增i。

否则令t=a[i+F[i]],同时将a[i...i+F[i]-1]区间的元素,向后移动一位,然后令a[i]=t,递增i。
下面说明一下如何根据阶乘数F(2110)和初始排列abcd,构建对应的排列。首先,我们发现F[0]=2,所以我们要将a[0+2]位置的元素c放在a[0]位置,之前,先用临时变量t记录a[2]的值,然后将a[0...0+2-1]区间内的元素向后移动一位,然后令a[0]=t,得到cabd,i值增加1;然后有F[1]=1,所以我们要将a[1+1]=a[2]=b放在a[1]位置,同时将a[1]向后移动一位,得到排列cbad;然后有F[2]=1,所以将a[2+1]=a[3]=d放在a[2]位置,同时a[2]向后移动一位。最终得到cbda,排列生成结束。整个算法代码如下

inline int FacNumNext(unsigned int* facnum, size_t array_size)
{
unsigned int i = 0;

while(i < array_size)
{
if(facnum[i] + 1 <= i)
{
facnum[i] += 1;
return 0;
}
else
{
facnum[i] = 0;
++i;
}
}

return 1;
}

/*
* 根据阶乘数所指定的逆序数根据原始字符串构建排列输出
*/
inline void BuildPerm(const char* array, size_t array_size, const unsigned int* facnum, char* out)
{
char t;
unsigned int i, j;

memcpy(out, array, array_size * sizeof(char));

for(i = 0; i < array_size - 1; ++i)
{
j = facnum[array_size - 1 - i];

if(j != 0)
{
t = out[i + j];
memmove(out + i + 1, out + i, j * sizeof(char));
out[i] = t;
}
}
}

/*
* 基于阶乘数(逆序数)的全排列生成算法
*/
void FullArray(char* array, size_t array_size)
{
unsigned int facnum[array_size];
char out[array_size];

for(unsigned int i = 0; i < array_size; ++i)
{
facnum[i] = 0;
}

BuildPerm(array, array_size, facnum, out);

for(unsigned int i = 0; i < array_size; ++i)
{
cout << out[i] << ' ';
}

cout << '\n';

while(!FacNumNext(facnum, array_size))
{
BuildPerm(array, array_size, facnum, out);

for(unsigned int i = 0; i < array_size; ++i)
{
cout << out[i] << ' ';
}

cout << '\n';
}
}
用该算法生成1234全排列,顺序如下图,该图来自与Wiki百科。



从生成排列顺序的角度讲,概算法相较于字典序和最小变更有明显优势,但是在实际应用中,由于根据阶乘数所定义的逆序构建排列是一个O(n^2)时间复杂度的过程,所以算法的整体执行效率逊色不少。但是通过阶乘数建立逆序数与排列对应关系的思路,还是十分精彩的,值得借鉴。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 2010 c