一个能够快速生成全排列的算法叫做邻位对换法,它之所以较快,是因为邻位对换法中下一个排列总是上一个排列某相邻两位对换得到的,只需一步,就可以得到一个新的全排列,而且绝不重复,但是由于每将n从一端移动到另一端后,就需要遍历排列2次,来寻找最大的可移数m,所以速度得到了限制。它的原理是:
[n]的全排列可由[n-1]的全排列生成:
给定[n-1]的一个排列п,将n 由最右端依次插入排列п ,即得到n个[n]的排列:
P1 P2 … P(n-1) n
P1 P2 … n P(n-1)
n P1 P2 … P(n-1)
对上述过程,一般地,对i,将前一步所得的每一排列重复i次,然后将i由第一排的最后往前移,至最前列,正好走了i次,下一个接着将i放在下一排列的最前面,然后依次往后移,一直下去即得i元排列。
考虑{1,2…n}的一个排列,其上每一个整数都给了一个方向,如果它的箭头所指的方向的邻点小于它本身,我们称整数k是可移的。
显然1永远不可移,n除了以下两种情形外,它都是可移的:
(1) n是第一个数,且其方向指向左侧
(2) n是最后一个数,且其方向指向右侧
于是,我们可按如下算法产生所有排列:
1,开始时:存在排列123…n,除1外,所有元素均可移,即方向都指向左侧。
2,当最大元素n可移动时,将其从一端依次移动到另一端,即可得到n-1个全排列;当n移动到某一端后,不能再移动,此时寻找最大的可移数m,将m与其箭头所指的邻数 (这个数当然不会是N,否则怎么可移)
互换位置,这样就又得到一个新的全排列;将所得新排列中所有比m大的数p的方向调整,即改为相反方向,这样使得n又成了可移数。
3,重复第2步直到所有的元素都不能移动为止。
以4个元素的排列为例,首先生成全排列1 2 3 4;
找到最大的可移数4,将4与其箭头所指的邻数互换位置,可以生成3个新排列:
1 2 4 3
1 4 2 3
4 1 2 3
因为没有比4更大的数p,所以无需调整p的方向。最大数4到了最左边后,由于其方向指向左侧,所以4不能再移动。
接下来寻找最大的可移数3,将3与其箭头所指的邻数2互换位置,可以得到新排列:4 1 3 2;
然后将所得排列中比3大的数4的方向调整,使得4可以移动,重新成为最大的可移数,将4与其箭头所指的邻数互换位置,可以生成3个新排列:
1 4 3 2
1 3 4 2
1 3 2 4
此时最大数4到了最右边,又因为其方向指向右侧,所以4不能再移动;于是我们寻找最大的可移数3,将3与其箭头所指的邻数1互换位置,可以得到新排列:3 1 2 4;
然后将所得排列中比3大的数4的方向调整,使得4可以移动,重新成为最大的可移数,将4与其箭头所指的邻数互换位置,可以生成3个新排列:
3 1 4 2
3 4 1 2
4 3 1 2
如此循环,直到所有的数都不能移动,即可求出全部排列。最后得到的一个全排列为:2 1 3 4,此时2指向左侧,1,3,4均指向右侧。
根据上述算法分析,使用一个辅助数组来存储各个元素的指向,我们可以得到代码如下:
/*
函数名称:Permutation
函数功能:排列邻位对换法:输出n个数的所有全排列
输入变量:int n:1,2,3,...,n共n个自然数
输出变量:无
*/
void Permutation(int n)
{
int *a = new int[n]; //用来存储n个自然数
bool *p = new bool[n]; //用来存储n个元素的指向:向左为false,向右为true
for (int i=0; i<n; i++) //存储全排列的元素值,并计算全排列的数量
{
a[i] = i + 1;
p[i] = false; //开始均指向左侧
}
do
{
Print(a, n); //输出第一个全排列
if (n == a[n-1])//若n在最右侧,将其逐次与左侧的元素交换,得到n - 1个新的排列
{
for (int i=n-1; i>0; i--)
{
int temp = a[i]; a[i] = a[i-1]; a[i-1] = temp;
bool flag = p[i]; p[i] = p[i-1]; p[i-1] = flag;
Print(a, n);
}
}
else //若n在最左侧,将其逐次与右侧的元素交换,得到n - 1个新的排列
{
for (int i=1; i<n; i++)
{
int temp = a[i]; a[i] = a[i-1]; a[i-1] = temp;
bool flag = p[i]; p[i] = p[i-1]; p[i-1] = flag;
Print(a, n);
}
}
} while (Move(a, p, n));
delete []a;
delete []p;
}
/*
函数名称:Move
函数功能:寻找最大可移数,可移数m,将m与其箭头所指的邻数互换位置,
并将所得新排列中所有比m大的数p的方向调整
输入变量:int a[]:存储了1,2,3,...,n共n个自然数的数组
bool p[]:存储了n个元素的指向的数组:向左为false,向右为true
int n:数组a[]的长度
输出变量:排列中存在最大可移数,则做了相关操作后返回真,否则直接返回假
*/
bool Move(int a[], bool p[], int n)
{
int max = 1;
int pos = -1;
for (int i=0; i<n; i++)
{
if (a[i] < max)
continue;
if ((p[i] && i < n-1 && a[i] > a[i+1]) || //指向右侧
(!p[i] && i > 0 && a[i] > a[i-1])) //指向左侧
{
max = a[i];
pos = i;
}
}
if (pos == -1) //都不能移动
return false;
//与其箭头所指的邻数互换位置
if (p[pos]) //指向右侧
{
int temp = a[pos]; a[pos] = a[pos+1]; a[pos+1] = temp;
bool flag = p[pos]; p[pos] = p[pos+1]; p[pos+1] = flag;
}
else //指向左侧
{
int temp = a[pos]; a[pos] = a[pos-1]; a[pos-1] = temp;
bool flag = p[pos]; p[pos] = p[pos-1]; p[pos-1] = flag;
}
//将所得排列中所有比max大的数p的方向调整
for (int i=0; i<n; i++)
{
if (a[i] > max)
p[i] = !p[i];
}
return true;
}