【转】全排列各种实现(非递归、递归)
FROM:http://www.cnblogs.com/answeryi/archive/2011/10/12/2209058.html
以6个数字的全排列为例说明,相当于用1,2,3,4,5,6 构造一个六位数,每一位上取一个数,这样一共有6!中方法。
很显然,这6!个数是有大小的,如果按从小到大排列,示意如下:
1 2 3 4 5 6
1 2 3 4 6 5
1 2 3 5 4 6
…………
6 5 4 3 2 1
显然 , 每个数有唯一后继 ! 如果找到一个数没有后继(6 5 4 3 2 1),则停止。
算法描述如下:
int[] num = {1,2,3,4,5,6};那么问题的重点在于如何判断是否有后继,以及怎样找到后继 !
while(hasNext()) {
print (num);
num = next();
}
是否有后继很好判断,唯一没有后继的只有654321,它的特点是每位的数字比后面的大!
如何找到后继,思路很清楚,即对于一个数,找到一个比它大的、最小的数!
从后往前搜索,找到一个极大值点(top) num[top-1] < num[top] ,
要使得下个数大于前一个数,找一个大于num[top-1]的数和top-1 交换,但要使得它最小,取其中最小的但大于num[top-1]的数。
交换之后,top以及其后面的数字还是单调递减的,将其位置对调,得到最小的数。
以 1 2 4 6 5 3 为例, 从后往前找到6 为一个极大值,考虑处理4 和后面的6 5 3 三个数字,大于4 的最小数字为5 ,对换得到5 和 6 4 3,
然后由于后面的6 4 3 还是单调递减的, 将其颠倒得到3 4 6 ,即 1 2 5 3 4 6 为 1 2 4 6 5 3的后继 。
源码:

找后继的全排列实现
#include < stdio.h>
#include < stdlib.h>
int x[6]= {1,2,3,4,5,6};
void printX()
{
int i = 0;
for(i=0;i<6;i++)
printf("%d ",x[i]);
printf("\n");
}
int hasNext()
{
int m = 5,i;
for(i=m;i>0;i--)
if(x[i]>x[i-1])
return 1;
return 0;
}
void next()
{
int i = 0;int top,mm;
int tmp;
//找到峰值
for (i=5;i>0;i--)if(x[i] > x[i-1]){top = i;break;}
//找到交换的数,大于峰值前一个数的 最小的数
mm = top;
for (i=top+1;i<6;i++) if(x[i]<x[top-1]){mm=i-1;break;}
tmp = x[top-1]; x[top-1] = x[mm]; x[mm] = tmp;
//颠倒后面的数
for(i=0;i<=(top+5)/2-top;i++){
tmp = x[i+top]; x[i+top] = x[5-i]; x[5-i] = tmp;
}
}
int main(int argc,char *argv[])
{
printX();
while (hasNext())
{
next();
printX();
}
return 0;
}
方法2 : (非递归)变进制数 【排列与数的对应】
- 常进制数
- 例如:十进制数、二进制数等等。实际上他们是以一个固定的整数作为进位值的。一般的,r 进制数就是每个位置满r 进一。
- 设有r 进制数An,An-1,An-2,...,A1,其中1=< Ai <= r-1, 1<= i <=n,i 位置的权重Pi=r^(i -1)。其对应的数值K 计算方法为: K = ΣAi*Pi = An*Pn+An-1*Pn-1+...+A1*P1
- 变进制数
- 推广的,我们不一定要用固定的数值作为进位值,设有正整数序列B1,B2,...,Bn,...,其中Bi >=2,我们就可以把它当作各个位置上的进位值,即第i 个位置满Bi 进一,添加B0 =1,则权重Pi=B0*B1*...*Bi-1 , i >=1,变进制数An,An-1,An-2,...,A1,其中0=< Ai <= Bi -1, 1<= i <=n,其数值的计算方法仍然是上述K 的公式。
- 实际上,我们日常生活中有很多变进制数,比如时间的描述“1年9个月8天6小时6分30秒”。
- 阶乘数系
- 最特殊、最简单的变进制数,就是取Bi=i +1, i >=1,则Pi=1*2*...*(i-1)*i =i !,其权重恰好是整数的阶乘,因而被称为阶乘数。阶乘数第i 个位置最大数值是i 。
我们知道n个不同的元素有n!个排列,我们这里考虑数字1,2,...,n的排列。现在我们要给他们一个排序方法,也即是找到他们和自然数列0,1, 2, ..., n!-1 的一个对应,习惯上的,我们按照排列逆序的程度来排序。在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。在这列我们取数字i 左侧大于i 的数字的个数(逆序),其他定义类似。
例:有排列 35241 ,1左侧有4个比它大,2左侧有2个比它大,数字3有0个,数字4有1个,数字5有0个,我们将这些逆序数倒着写下来是:42010.
以下表格以三个数位为例说明:
| 数字 | 变进制表示 | 逆序数组 | 位数数组 | 对应排列 |
| 0 | 0*2! + 0 *1! | 000 | 012 | 123 |
| 1 | 0*2! + 1 *1! | 010 | 021 | 132 |
| 2 | 1*2! + 0 *1! | 100 | 102 | 213 |
| 3 | 1*2! + 1 *1! | 110 | 120 | 312 |
| 4 | 2*2! + 0 *1! | 200 | 201 | 231 |
| 5 | 2*2! + 1 *1! | 210 | 210 | 321 |
C++程序实现:

变进制数求排列
int fact(int n)
{ //阶乘函数
int x = 1;
for(int i=n;i>0;i--)
x*=i;
return x;
}
void perm(int n)
{
int *fa = new int[n+1]; //保存阶乘结果
int *r = new int[n],*r2 = new int[n],*num = new int[n];
//r 计算逆序数;r2计算对应位数;num保存排列结果
int tot = 0;
for (int i=0;i<n+1;i++)
fa[i] =fact(i);
for (int count=0;count<fa[n];count++) {
//一共n!个排列,对每个数,计算其对应的序列
tot = count; //r,r2 保存变进制数结果,即对应的逆序数组
for (int b=n-1;b>=1;b--) {
r2[n-1-b] = r[n-1-b] = tot/fa[b];
tot = tot % fa[b];
}
r[n-1] = r2[n-1] = 0;
//根据逆序数,计算每个数字所在位数
for (int b=1;b<n-1;b++) {
for (int k=b-1;k>=0;k--) {
if(r[k]<=r[b])
r2[b] ++;
}
}
for (int i=0;i<n-1;i++) {
r2[n-1] += (i+1 - r2[i]);
}
//根据位数计算出排列
for (int i=0;i<n;i++) {
num[r2[i]] = i+1;
}
//打印结果
for (int i=0;i<n;i++)
cout << num[i] << " ";
cout << "[num]"<<endl;
}
}
方法3 : (递归)交换
int n = 0;
void swap(int *a, int *b)
{
int m = *a;
*a = *b;
*b = m;
}
void perm(int list[], int k, int m)
{
int i;
if(k > m) {
for(i = 0; i <= m; i++)
printf("%d ", list[i]);
printf("\n");
n++;
} else {
for(i = k; i <= m; i++)
{
swap(&list[k], &list[i]);
perm(list, k + 1, m);
swap(&list[k], &list[i]);
}
}
}
int main()
{
int list[] = {1, 2, 3, 4, 5};
perm(list, 0, 4);
printf("total:%d\n", n);
return 0;
}

浙公网安备 33010602011771号