C++和java中关于全排列和组合的有关算法

来源:互联网 发布:哪里卖淘宝号的 编辑:程序博客网 时间:2024/06/06 13:15

收集的排列加组合的各种算法,还有待完善:

用C++写一个函数, 如 Foo(const char *str), 打印出 str 的全排列,
如 abc 的全排列: abc, acb, bca, dac, cab, cba

一.全排列的递归实现

为方便起见,用123来示例下。123的全排列有123、132、213、231、312、321这六种。首先考虑213和321这二个数是如何得出的。显然这二个都是123中的1与后面两数交换得到的。然后可以将123的第二个数和每三个数交换得到132。同理可以根据213和321来得231和312。因此可以知道——全排列就是从第一个数字起每个数分别与它后面的数字交换。找到这个规律后,递归的代码就很容易写出来了:

[cpp] view plaincopy
  1. //全排列的递归实现  
  2. #include <stdio.h>  
  3. #include <string.h>  
  4. void Swap(char *a, char *b)  
  5. {  
  6.     char t = *a;  
  7.     *a = *b;  
  8.     *b = t;  
  9. }  
  10. //k表示当前选取到第几个数,m表示共有多少数.  
  11. void AllRange(char *pszStr, int k, int m)  
  12. {  
  13.     if (k == m)  
  14.     {  
  15.         static int s_i = 1;  
  16.         printf("  第%3d个排列\t%s\n", s_i++, pszStr);  
  17.     }  
  18.     else  
  19.     {  
  20.         for (int i = k; i <= m; i++) //第i个数分别与它后面的数字交换就能得到新的排列  
  21.         {  
  22.             Swap(pszStr + k, pszStr + i);  
  23.             AllRange(pszStr, k + 1, m);  
  24.             Swap(pszStr + k, pszStr + i);  
  25.         }  
  26.     }  
  27. }  
  28. void Foo(char *pszStr)  
  29. {  
  30.     AllRange(pszStr, 0, strlen(pszStr) - 1);  
  31. }  
  32. int main()  
  33. {  
  34.     printf("         全排列的递归实现\n");  
  35.     printf("  --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n\n");  
  36.     char szTextStr[] = "123";  
  37.     printf("%s的全排列如下:\n", szTextStr);  
  38.     Foo(szTextStr);  
  39.     return 0;  
  40. }  

运行结果如下:

注意这样的方法没有考虑到重复数字,如122将会输出:

这种输出绝对不符合要求,因此现在要想办法来去掉重复的数列。

二.去掉重复的全排列的递归实现

由于全排列就是从第一个数字起每个数分别与它后面的数字交换。我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。如122,第一个数与后面交换得212、221。然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到221。与由122中第一个数与第三个数交换所得的221重复了。所以这个方法不行。

换种思维,对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。此时全排列生成完毕。

这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。用编程的话描述就是第i个数与第j个数交换时,要求[i,j)中没有与第j个数相等的数。下面给出完整代码:

[cpp] view plaincopy
  1. //去重全排列的递归实现  
  2. #include <stdio.h>  
  3. #include <string.h>  
  4. void Swap(char *a, char *b)  
  5. {  
  6.     char t = *a;  
  7.     *a = *b;  
  8.     *b = t;  
  9. }  
  10. //在pszStr数组中,[nBegin,nEnd)中是否有数字与下标为nEnd的数字相等  
  11. bool IsSwap(char *pszStr, int nBegin, int nEnd)  
  12. {  
  13.     for (int i = nBegin; i < nEnd; i++)  
  14.         if (pszStr[i] == pszStr[nEnd])  
  15.             return false;  
  16.     return true;  
  17. }  
  18. //k表示当前选取到第几个数,m表示共有多少数.  
  19. void AllRange(char *pszStr, int k, int m)  
  20. {  
  21.     if (k == m)  
  22.     {  
  23.         static int s_i = 1;  
  24.         printf("  第%3d个排列\t%s\n", s_i++, pszStr);  
  25.     }  
  26.     else  
  27.     {  
  28.         for (int i = k; i <= m; i++) //第i个数分别与它后面的数字交换就能得到新的排列  
  29.         {  
  30.             if (IsSwap(pszStr, k, i))  
  31.             {  
  32.                 Swap(pszStr + k, pszStr + i);  
  33.                 AllRange(pszStr, k + 1, m);  
  34.                 Swap(pszStr + k, pszStr + i);  
  35.             }  
  36.         }  
  37.     }  
  38. }  
  39. void Foo(char *pszStr)  
  40. {  
  41.     AllRange(pszStr, 0, strlen(pszStr) - 1);  
  42. }  
  43. int main()  
  44. {  
  45.     printf("         去重全排列的递归实现\n");  
  46.     printf("  --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n\n");  
  47.     char szTextStr[] = "122";  
  48.     printf("%s的全排列如下:\n", szTextStr);  
  49.     Foo(szTextStr);  
  50.     return 0;  
  51. }  

运行结果如下:

 

OK,到现在我们已经能熟练写出递归的方法了,并且考虑了字符串中的重复数据可能引发的重复数列问题。那么如何使用非递归的方法来得到全排列了?

 

三.全排列的非递归实现

要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。如"1234"的下一个排列就是"1243"。只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。

如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字符串"6220"颠倒即得到"950226"。

对于像"4321"这种已经是最“大”的排列,采用STL中的处理方法,将字符串整个颠倒得到最“小”的排列"1234"并返回false。

这样,只要一个循环再加上计算字符串下一个排列的函数就可以轻松的实现非递归的全排列算法。按上面思路并参考STL中的实现源码,不难写成一份质量较高的代码。值得注意的是在循环前要对字符串排序下,可以自己写快速排序的代码(请参阅《白话经典算法之六 快速排序 快速搞定》),也可以直接使用VC库中的快速排序函数(请参阅《使用VC库函数中的快速排序函数》)。下面列出完整代码:

[cpp] view plaincopy
  1. //全排列的非递归实现  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  4. #include <string.h>  
  5. void Swap(char *a, char *b)  
  6. {  
  7.     char t = *a;  
  8.     *a = *b;  
  9.     *b = t;  
  10. }  
  11. //反转区间  
  12. void Reverse(char *a, char *b)  
  13. {  
  14.     while (a < b)  
  15.         Swap(a++, b--);  
  16. }  
  17. //下一个排列  
  18. bool Next_permutation(char a[])  
  19. {  
  20.     char *pEnd = a + strlen(a);  
  21.     if (a == pEnd)  
  22.         return false;  
  23.     char *p, *q, *pFind;  
  24.     pEnd--;  
  25.     p = pEnd;  
  26.     while (p != a)  
  27.     {  
  28.         q = p;  
  29.         --p;  
  30.         if (*p < *q) //找降序的相邻2数,前一个数即替换数  
  31.         {  
  32.             //从后向前找比替换点大的第一个数  
  33.             pFind = pEnd;  
  34.             while (*pFind <= *p)  
  35.                 --pFind;  
  36.             //替换  
  37.             Swap(pFind, p);  
  38.             //替换点后的数全部反转  
  39.             Reverse(q, pEnd);  
  40.             return true;  
  41.         }  
  42.     }  
  43.     Reverse(p, pEnd);//如果没有下一个排列,全部反转后返回true  
  44.     return false;  
  45. }  
  46. int QsortCmp(const void *pa, const void *pb)  
  47. {  
  48.     return *(char*)pa - *(char*)pb;  
  49. }  
  50. int main()  
  51. {  
  52.     printf("         全排列的非递归实现\n");  
  53.     printf("  --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n\n");  
  54.     char szTextStr[] = "abc";  
  55.     printf("%s的全排列如下:\n", szTextStr);  
  56.     //加上排序  
  57.     qsort(szTextStr, strlen(szTextStr), sizeof(szTextStr[0]), QsortCmp);  
  58.     int i = 1;  
  59.     do{  
  60.         printf("第%3d个排列\t%s\n", i++, szTextStr);  
  61.     }while (Next_permutation(szTextStr));  
  62.     return 0;  
  63. }  

测试一下,结果如下所示:

将字符串改成"cba"会输出:

 

至此我们已经运用了递归与非递归的方法解决了全排列问题,总结一下就是:

1.全排列就是从第一个数字起每个数分别与它后面的数字交换。

2.去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。

3.全排列的非递归就是由后向前找替换数替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。


原文地址:http://blog.csdn.net/morewindows/article/details/7370155

一.利用二进制状态法求排列组合,此种方法比较容易懂,但是运行效率不高,小数据排列组合可以使用

import java.util.Arrays;
public class binArray {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        count2();
        long end = System.currentTimeMillis();
        System.out.println(end - start);//计算运行时间
    }
    private static void count2() {
        int[] num = new int[] { 1, 2, 3 };
        for (int i = 1; i < Math.pow(3, 3); i++) {
            String str = Integer.toString(i, 3);
            int sz = str.length();
            for (int j = 0; j < 3 - sz; j++) {
                str = "0" + str;
            }
            char[] temp = str.toCharArray();
            Arrays.sort(temp);
            String gl = new String(temp);
            if (!gl.equals("012")) {
                continue;
            }
            String result = "";
            for (int m = 0; m < str.length(); m++) {
                result += num[Integer.parseInt(str.charAt(m) + "")];
            }
            System.out.println(result);
        }
    }
    public static void count1() {
        int[] num = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        int[] ss = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
        int[] temp = new int[9];
        while (temp[0] < 9) {
            temp[temp.length - 1]++;
            for (int i = temp.length - 1; i > 0; i--) {
                if (temp[i] == 9) {
                    temp[i] = 0;
                    temp[i - 1]++;
                }
            }
            int[] tt = temp.clone();
            Arrays.sort(tt);
            if (!Arrays.equals(tt, ss)) {
                continue;
            }
            String result = "";
            for (int i = 0; i < num.length; i++) {
                result += num[temp[i]];
            }
            System.out.println(result);
        }
    }
}

/**

 * 二进制位全排列算法(代码量最小)
 *
 * @author Zebra
 * @date 2014-12-3 下午4:51:49
 */
public class test1 {
    public static void main(String[] args) {
        String arr[] = { "a", "b", "c", "d", "e" };
        int all = 5;
        for (int i = 0; i < 1 << all; i++) {
            StringBuffer sb = new StringBuffer();
            for (int j = 0; j < all; j++) {
                if ((i & (1 << j)) != 0) {
                    sb.append(arr[j]);
                }
            }
            System.out.println(sb);
        }
    }
}

二.利用动态规划的思想求排列和组合

package Acm;
//强大的求组合数
public class MainApp {
    public static void main(String[] args) {
        int[] num=new int[]{1,2,3,4,5};
        String str="";
        //求3个数的组合个数
//        count(0,str,num,3);
//        求1-n个数的组合个数
        count1(0,str,num);
    }

    private static void count1(int i, String str, int[] num) {
        if(i==num.length){
            System.out.println(str);
            return;
        }
        count1(i+1,str,num);
        count1(i+1,str+num[i]+",",num);
    }

    private static void count(int i, String str, int[] num,int n) {
        if(n==0){
            System.out.println(str);
            return;
        }
        if(i==num.length){
            return;
        }
        count(i+1,str+num[i]+",",num,n-1);
        count(i+1,str,num,n);
    }
}


下面是求排列
package Acm;
//求排列,求各种排列或组合后排列
import java.util.Arrays;
import java.util.Scanner;public class Demo19 {
    private static boolean f[];
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        int sz=sc.nextInt();
        for(int i=0;i<sz;i++){
            int sum=sc.nextInt();
            f=new boolean[sum];
            Arrays.fill(f, true);
            int[] num=new int[sum];
            for(int j=0;j<sum;j++){
                num[j]=j+1;
            }
            int nn=sc.nextInt();
            String str="";
            count(num,str,nn);
        }
    }
    /**
     *
     * @param num 表示要排列的数组
     * @param str 以排列好的字符串
     * @param nn  剩下需要排列的个数,如果需要全排列,则nn为数组长度
     */
    private static void count(int[] num, String str, int nn) {
        if(nn==0){
            System.out.println(str);
            return;
        }
        for(int i=0;i<num.length;i++){
            if(!f[i]){
                continue;
            }
            f[i]=false;
            count(num,str+num[i],nn-1);
            f[i]=true;
        }
    }

}


这是数组全排列的算法:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Title:全排列算法
 */
public class AllStringArray {
    // 将NUM设置为待排列数组的长度即实现全排列
    private static int NUM = 4;

    /**
     * 递归算法:将数据分为两部分,递归将数据从左侧移右侧实现全排列
     *
     * @param datas
     * @param target
     */
    private static void sort(List datas, List target) {
        if (target.size() == NUM) {
            for (Object obj : target)
                System.out.print(obj);
            System.out.println();
            return;
        }
        for (int i = 0; i < datas.size(); i++) {
            List newDatas = new ArrayList(datas);
            List newTarget = new ArrayList(target);
            newTarget.add(newDatas.get(i));
            newDatas.remove(i);
            sort(newDatas, newTarget);
        }
    }

    public static void main(String[] args) {
        String[] datas = new String[] { "a", "b", "c", "d" };
        ArrayList temp = new ArrayList();
        // asList用法将数组看做列表
        sort(Arrays.asList(datas), temp);
    }
}

总结java语言下的全排列和组合问题,推荐使用动态规划思想和后面的递归全排列算法。

0 0