我们先看一个例子。
示例: 1 2 3的全排列如下:
1 2 3 , 1 3 2 , 2 1 3 , 2 3 1 , 3 1 2 , 3 2 1
我们这里是通过字典序法找出来的。
那么什么是字典序法呢?
对于数字1、2、3……n的排列,不同排列的先后关系是从左到右逐个比较对应的数字的先后来决定的。例如对于5个数字的排列 12354和12345,排列12345在前,排列12354在后。按照这样的规定,5个数字的所有的排列中最前面的是12345,最后面的是 54321。
算法说明:
设置了中介数的字典序全排列生成算法,与递归直接模拟法和循环直接模拟法的最大不同是,不需要模拟有序全排列的生成过程,也就不需要逐一地生成各个全排列,只要知道初始全排列,就能根据序号(m-1),直接得到第m个全排列,因此速度非常快。
它的缺点是在生成序号(m-1)的递增进进制数时,需要事先创建一个用来存储n的阶乘数n! 的数组p[],所以n的值不能太大,否则就会溢出,根据我的测试结果,当1<=n<=20时不会溢出,当21<=n时会溢出。
设置了中介数的字典序全排列生成算法需要设置中介数,在实际应用中比较繁琐,不如由前一个排列直接推得下一个排列方便。
下图是用字典序法找1 2 3的全排列(全过程):
代码实现如下:
#include <stdio.h>
//交换list[a],list[b]
void Swap(int list[], int a, int b)
{
int temp = 0;
temp = list[a];
list[a] = list[b];
list[b] = temp;
return;
}
//将list区间[a,n]之间的数据由小到大排序
void Sort(int list[], int a, int n)
{
int temp = 0;
int i;
int j;
for ( i = 1; i < n-a; ++i)
for ( j = a+1; j < n-1; ++j)
if (list[j] > list[j+1])
{
temp = list[j];
list[j] = list[j+1];
list[j+1] = temp;
}
return;
//全排列
void Prim(int list[], int n)
{
int num = 1, a = 0, b = 0;
for ( i = n; i > 0; --i) //计算有多少种情况,就循环多少次
num *= i;
while (num--)
{
for ( i = 0; i < n; ++i) //打印情况
printf("%d ",list[i]);
printf("\n");
for ( i = n-1; i > 0; --i) //从右往左,找出第一个左边小于右边的数,设为list[a]
if (list[i-1] < list[i])
{
a = i-1;
break;
}
for ( j = n-1; j > a; --j) //从右往左,找出第一个大于list[a]的数,设为list[b]
if (list[j] > list[a])
{
b = j;
break;
}
Swap(list, a, b); //交换list[a],list[b]
Sort(list, a, n); //将list[a]后面的数据,由小往大排列
}
return;
}
//主函数
int main()
{
int list[] = {1,2,3,4};
Prim(list,3);
return 0;
}