• 浅谈全排列与组合的生成


    例:输出从1,2......m,中任取k个数的所有组合。m=5,k=3时有543,542,541,532,531,521,432,431,421,321有C(m,k)个。

    法一:枚举方法

     1 #include <iostream>
     2 #include <cstdio>
     3 using namespace std;
     4 int main()
     5 {
     6     int n;
     7     cin >> n;
     8     for (int x = 1; x <= n - 2; ++x)
     9         for (int y = x + 1; y <= n - 1; ++y)
    10             for (int z = y + 1; z <= n; ++z)
    11                 printf("%d %d %d\n", x, y, z);
    12     system("pause");
    13     return 0;
    14 }

    n = 5时将输出123,124,125,134,135,145,234,235,245,345。

    法二:递归实现

    设函数comb(int m,int k)是找出1,2,.....m中任取k个数的所有组合,当组合的第一个数字选定后,其后的数字的就是从余下的m-1个数选k-1个数的组合,问题转化为求comb(m-1, k-1).需要一个全局数组存放结果,将确定的k个数字组合的第一个数存储在a[k]中,求出一个组合后输出a数组中的一个组合。每个组合的第一个数字可以是m,m-1,.....k.函数确定组合的第一个数字后,如果还为确定组合的其余元素,则继续递归,否则输出。

     1 #include <iostream>
     2 #include <cstdio>
     3 using namespace std;
     4 const int MAX = 10;
     5 int a[10];
     6 void comb(int m, int k)
     7 {
     8     int i, j;
     9     for (i = m; i >= k; --i)
    10     {
    11         a[k] = i;                    //每个组合的第一个数可以是m,m-1.....k
    12         if (k > 1)
    13             comb(i - 1, k - 1);        //k>1继续递归
    14         else
    15         {
    16             for (j = a[0]; j > 0; --j)
    17                 printf("%d", a[j]);
    18             printf("\n");
    19         }
    20     }
    21 }
    22 int main()
    23 {
    24     int m, k;
    25     cin >> m >> k;
    26     a[0] = k;                //a[0]用来表示k 
    27     comb(m, k);
    28     return 0;
    29 }

    输入m=5,k=3,输出543,542,541,532,531,521,432,431,421,321。

    全排列的生成:

    最简单的方法就是使用STL<algorithm>中的next_permutation,其返回bool值,不过经常用就啥都忘了。

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>            //for sort and next_permutation 
     4 using namespace std;
     5 int main()
     6 {
     7     int n;
     8     scanf("%d", &n);
     9     int p[n];
    10     for (int i = 0; i < n; ++i)
    11         scanf("%d", &p[i]);
    12     sort(p, p + n);                //处理乱序输入,适用于可重复集合
    13     do {
    14         for (int i = 0; i < n; ++i)
    15             printf("%d ", p[i]);
    16         printf("\n");
    17     } while (next_permutation(p, p + n));   //bool next_purmutation(begin, end) 
    18     system("pause");
    19     return 0;
    20 } 

    递归生成1-n的全排列(无重复元素):如abc的全排列有abc,acb,bac,bca,cba,cab,一共3!个。

     设对R={r1,r2,......rn}全排列,设Ri = R - {ri},perm(X)表示集合X的全排序,(ri)perm(X)表示X全排序前加前缀ri

    全排列的递归定义:设n是集合中元素个数

                      r                     n = 1

    perm(R)=  (r1)perm(R1)    

                     ......

          (rn)perm(Rn)

    如果给定集合{a,b,c,d},所有可能的排列由下列排列组成

    1.以a开头的后面跟着(b,c,d)的所有排列

    2.以b开头的后面跟着(a,c,d)的所有排列

    3.以c开头的后面跟着(a,b,d)的所有排列

    4.以d开头的后面跟着(a,b,c)的所有排列

    递归线索是“后面跟着。。。的所有排列”,若能够解决n-1个元素集合的排列,就可以解决n个元素集合的排列问题。

    处理时将所有数分别与第一个数交换,这样就总是在处理后n-1个数的全排列。

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <algorithm>            //for swap(x,y)
     4 using namespace std;
     5 void perm(char *a, int i, int n)
     6 {
     7     if (i == n)                            //递归出口
     8     {
     9         for (int j = 0; j <= n; ++j)
    10             printf("%c", a[j]);
    11         printf("\n");
    12     }
    13     else
    14     {
    15         for (int j = i; j <= n; ++j)
    16         {
    17             swap(a[i], a[j]);
    18             perm(a, i + 1, n);
    19             swap(a[i], a[j]);            //第二个swap用于恢复与第一个数交换
    20         }
    21     }
    22 }
    23 int main()
    24 {
    25     char a[3] = {'a', 'b', 'c'};
    26     perm(a, 0, 2);                      //初始调用perm(list, 0, n-1)
    27     system("pause");
    28     return 0;
    29 }

    执行后输出abc,acb,bac,bca,cba,cab,按照字典序输出。

    根据这种思路还有一种方法确定递归函数:(参考刘汝佳《算法竞赛入门经典》)

    1已经确定的前缀序列,以便输出

    2需要进行全排列的元素集合,以便依次选作第一个元素

    伪代码:

    void perm(序列A, 集合S)

    {

      if(S为空)

        输出序列A;

      else 按照从小到大的顺序依次考虑S的每个元素v

        perm(在A的末尾添加v后得到的新序列,S-{v});

    }

    代码如下:

     1 #include <iostream>
     2 #include <cstdio>
     3 using namespace std;
     4 void perm(int n, int *a, int cur)
     5 {
     6     int i, j;
     7     if (cur == n)                //递归边界
     8     {
     9         for (i = 0; i < n; ++i)
    10             printf("%d", a[i]);  //处理字母时只需要改成printf("%c", a[i]+'a'-1);
    11         printf("\n");
    12     }
    13     else for (i = 1; i <= n; ++i)  //在a[cur]中填各种整数i
    14     {
    15         bool ok = 1;
    16         for (j = 0; j < cur; ++j)
    17             if (a[j] == i)
    18                 ok = 0;              //若i在a[0]...a[cur-1]出现过,则不能再选
    19         if (ok)                      //ok为真说明i没有在序列中出现过
    20         {                         //就把i添加到序列末尾后递归调用,这样处理就不必保存集合S了
    21             a[cur] = i;
    22             perm(n, a, cur + 1);
    23         }
    24     }
    25 }
    26 int main()
    27 {
    28     int a[] = {1, 2, 3};
    29     perm(3, a, 0);                //初始调用perm(n, a, 0)
    30     system("pause");
    31     return 0;
    32 }

    输出结果123,132,213,231,312,321,改成字母后输出abc,acb,bac,bca,cba,cab。

  • 相关阅读:
    bow lsa plsa
    拉普拉斯平滑处理 Laplace Smoothing
    程序员的出路在哪里
    Android各代码层获取系统时间的方法
    TCP/IP笔记 二.网络层(2)——ICMP,RIP,OSPF,BGP
    利用Xtrabackup备份集合恢复一台从库的过程
    char *和char[]的区别,困扰很长时间了,总结下
    不使用webview,用手机浏览器的android app
    [置顶] oracle 快速查询数据库各种信息、及转换对应java代码
    MySQL备份方案-->(利用mysqldump以及binlog二进制日志)
  • 原文地址:https://www.cnblogs.com/PegasusWang/p/2977890.html
Copyright © 2020-2023  润新知