java 全排列非递归算法_全排列各种实现(非递归、递归)

方法一:(非递归)字典排序找后继

以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的后继 。

源码:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png找后继的全排列实现

#include < stdio.h>#include< stdlib.h>

int x[6]= {1,2,3,4,5,6};voidprintX()

{int i = 0;for(i=0;i<6;i++)

printf("%d",x[i]);

printf("\n");

}inthasNext()

{int m = 5,i;for(i=m;i>0;i--)if(x[i]>x[i-1])return 1;return 0;

}voidnext()

{int i = 0;inttop,mm;inttmp;//找到峰值

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]

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++程序实现:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png变进制数求排列

int fact(intn)

{//阶乘函数

int x = 1;for(int i=n;i>0;i--)

x*=i;returnx;

}void perm(intn)

{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

fa[i]=fact(i);for (int count=0;count

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=0;k--) {if(r[k]<=r[b])

r2[b]++;

}

}for (int i=0;i

r2[n-1] += (i+1 -r2[i]);

}//根据位数计算出排列

for (int i=0;i

num[r2[i]]= i+1;

}//打印结果

for (int i=0;i

cout<< num[i] << " ";

cout<< "[num]"<

}

}

方法3 : (递归)交换

#include 

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;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值