全排列各种实现(非递归、递归)
方法一:(非递归)字典排序找后继
以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),则停止。
算法描述如下:
以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 : (递归)交换
#include <stdio.h>
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;
}
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;
}