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

方法一:(非递归)字典排序找后继
以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[] = {12345};     
    perm(list, 04);     
    printf("total:%d\n", n);     
    return 0

 

posted @ 2011-10-12 21:17  Keosu  阅读(7448)  评论(0编辑  收藏  举报