全排列各种实现(非递归、递归)

方法一:(非递归)字典排序找后继
以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 进制数A n,A n-1,A n-2,...,A 1,其中1=< A i <= r-1, 1<= i <=n, i 位置的权重P i=r^( i -1)。其对应的数值K 计算方法为: K = ΣA i*P i = A n*P n+A n-1*P n-1+...+A 1*P 1
变进制数
推广的,我们不一定要用固定的数值作为进位值,设有正整数序列B 1,B 2,...,B n,...,其中B i >=2,我们就可以把它当作各个位置上的进位值,即第 i 个位置满B i 进一,添加B 0 =1,则权重P i=B 0*B 1*...*B i-1 , i >=1,变进制数A n,A n-1,A n-2,...,A 1,其中0=< A i <= B i -1, 1<= i <=n,其数值的计算方法仍然是上述K 的公式。
实际上,我们日常生活中有很多变进制数,比如时间的描述“1年9个月8天6小时6分30秒”。
阶乘数系
 最特殊、最简单的变进制数,就是取B i= i +1, i >=1,则P i=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.

以下表格以三个数位为例说明:

数字变进制表示逆序数组位数数组对应排列
00*2! + 0 *1!000012123
10*2! + 1 *1!010021132
21*2! + 0 *1!100102213
31*2! + 1 *1!110120312
42*2! + 0 *1!200201231
52*2! + 1 *1!210210321

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[] = { 12345};     
    perm(list,  04);     
    printf( " total:%d\n ", n);     
     return  0

 

 

 

转载于:https://www.cnblogs.com/answeryi/archive/2011/10/12/2209058.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值