C语言实现的排列组合问题的通用算法、解决方法

尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手。由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论。以在n个数中选取m(0<m<=n)个数为例,问题可分解为:

1. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止。

2. 从n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m。

很明显,上述方法是一个递归的过程,也就是说用递归的方法可以很干净利索地求得所有组合。

下面是递归方法的实现:

代码如下:

/// 求从数组a[1..n]中任选m个元素的所有组合。
/// a[1..n]表示候选集,n为候选集大小,n>=m>0。
/// b[1..M]用来存储当前组合中的元素(这里存储的是元素下标),
/// 常量M表示满足条件的一个组合中元素的个数,M=m,这两个参数仅用来输出结果。
void combine( int a[], int n, int m,  int b[], const int M )
{
 for(int i=n; i>=m; i--)   // 注意这里的循环范围
 {
  b[m-1] = i - 1;
  if (m > 1)
   combine(a,i-1,m-1,b,M);
  else                     // m == 1, 输出一个组合
  {  
   for(int j=M-1; j>=0; j--)
    cout << a[b[j]] << " ";
   cout << endl;
  }
 }
}

因为递归程序均可以通过引入栈,用回溯转化为相应的非递归程序,所以组合问题又可以用回溯的方法来解决。为了便于理解,我们可以把组合问题化归为图的路径遍历问题,在n个数中选取m个数的所有组合,相当于在一个这样的图中(下面以从1,2,3,4中任选3个数为例说明)求从[1,1]位置出发到达[m,x](m<=x<=n)位置的所有路径:

代码如下:

1  2  3  4
    2  3  4
        3  4

上图是截取n×n右上对角矩阵的前m行构成,如果把矩矩中的每个元素看作图中的一个节点,我们要求的所有组合就相当于从第一行的第一列元素[1,1]出发,到第三行的任意一列元素作为结束的所有路径,规定只有相邻行之间的节点,并且下一行的节点必须处于上一行节点右面才有路径相连,其他情况都无路径相通。显然,任一路径经过的数字序列就对应一个符合要求的组合。

下面是非递归的回溯方法的实现:

代码如下:

/// 求从数组a[1..n]中任选m个元素的所有组合。
/// a[1..n]表示候选集,m表示一个组合的元素个数。
/// 返回所有组合的总数。
int combine(int a[], int n, int m)
{  
 m = m > n ? n : m;

int* order = new int[m+1];   
 for(int i=0; i<=m; i++)
  order[i] = i-1;            // 注意这里order[0]=-1用来作为循环判断标识
 
 int count = 0;                               
 int k = m;
 bool flag = true;           // 标志找到一个有效组合
 while(order[0] == -1)
 {
  if(flag)                   // 输出符合要求的组合
  {  
   for(i=1; i<=m; i++)                   
    cout << a[order[i]] << " ";
   cout << endl;
   count++;
   flag = false;
  }

order[k]++;                // 在当前位置选择新的数字
  if(order[k] == n)          // 当前位置已无数字可选,回溯
  {
   order[k--] = 0;
   continue;
  }    
 
  if(k < m)                  // 更新当前位置的下一位置的数字         
  {
   order[++k] = order[k-1];
   continue;
  }
 
  if(k == m)
   flag = true;
 }

delete[] order;
 return count;
}

下面是测试以上函数的程序:

代码如下:

int main()
{
 const int N = 4;
 const int M = 3;
 int a[N];
 for(int i=0;i<N;i++)
  a[i] = i+1;

// 回溯方法
 cout << combine(a,N,3) << endl;

// 递归方法
 int b[M];
 combine(a,N,M,b,M);

return 0;
}

由上述分析可知,解决组合问题的通用算法不外乎递归和回溯两种。在针对具体问题的时候,因为递归程序在递归层数上的限制,对于大型组合问题而言,递归不是一个好的选择,这种情况下只能采取回溯的方法来解决。

n个数的全排列问题相对简单,可以通过交换位置按序枚举来实现。STL提供了求某个序列下一个排列的算法next_permutation,其算法原理如下:
1. 从当前序列最尾端开始往前寻找两个相邻元素,令前面一个元素为*i,后一个元素为*ii,且满足*i<*ii;

2. 再次从当前序列末端开始向前扫描,找出第一个大于*i的元素,令为*j(j可能等于ii),将i,j元素对调;

3. 将ii之后(含ii)的所有元素颠倒次序,这样所得的排列即为当前序列的下一个排列。

其实现代码如下:

代码如下:

template <class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first, BidirectionalIterator last)
{
  if (first == last) return false;   // 空範圍
  BidirectionalIterator i = first;
  ++i;
  if (i == last) return false;       // 只有一個元素
  i = last;                          // i 指向尾端
  --i;

for(;;)
 {
  BidirectionalIterator ii = i;
  --i;
  // 以上,鎖定一組(兩個)相鄰元素
  if (*i < *ii)                     // 如果前一個元素小於後一個元素
  {
   BidirectionalIterator j = last;  // 令 j指向尾端
   while (!(*i < *--j));            // 由尾端往前找,直到遇上比 *i 大的元素
   iter_swap(i, j);                 // 交換 i, j
   reverse(ii, last);               // 將 ii 之後的元素全部逆向重排
   return true;
  }
  if (i == first)                   // 進行至最前面了
  {
   reverse(first, last);            // 全部逆向重排
   return false;
  }
 }
}

下面程序演示了利用next_permutation来求取某个序列全排列的方法:

代码如下:

int main()
{
 int ia[] = {1,2,3,4};
 vector<int> iv(ia,ia+sizeof(ia)/sizeof(int));

copy(iv.begin(),iv.end(),ostream_iterator<int>(cout," "));
 cout << endl;
 while(next_permutation(iv.begin(),iv.end()))
 {
  copy(iv.begin(),iv.end(),ostream_iterator<int>(cout," "));
  cout << endl;
 }

return 0;
}

注意:上面程序中初始序列是按数值的从小到大的顺序排列的,如果初始序列无序的话,上面程序只能求出从当前序列开始的后续部分排列,也就是说next_permutation求出的排列是按排列从小到大的顺序进行的。

(0)

相关推荐

  • 使用C语言解决字符串全排列问题

    问题 输入一个字符串,打印出该字符串中字符的所有排列.例如输入字符串abc,则输出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba 思路 这是典型的递归求解问题,递归算法有四个特性: 必须有可达到的终止条件,否则程序陷入死循环 子问题在规模上比原问题小 子问题可通过再次递归调用求解 子问题的解应能组合成整个问题的解 对于字符串的排列问题: 如果能生成n-1个元素的全排列,就能生成n个元素的全排列.对于只有一个元素的集合,可以直接生成全排列.所以全排列的递归终

  • C语言/C++中如何产生随机数

    C语言/C++怎样产生随机数:这里要用到的是rand()函数, srand()函数,和time()函数. 需要说明的是,iostream头文件中就有srand函数的定义,不需要再额外引入stdlib.h;而使用time()函数需要引入ctime头文件. 使用rand()函数获取一个随机数如果你只要产生随机数而不需要设定范围的话,你只要用rand()就可以了:rand()会返回一随机数值, 范围在0至RAND_MAX 间.RAND_MAX定义在stdlib.h, 其值为2147483647. 例子

  • C语言实现输入一个字符串后打印出该字符串中字符的所有排列

    本文实例讲述了C语言实现输入一个字符串后打印出该字符串中字符的所有排列的方法,属于数学里的排列问题.是一个很实用的算法技巧.分享给大家供大家参考.具体实现方法如下: 例如输入字符串abc,则输出由字符a.b.c所能排列出来的所有字符串abc.acb.bac.bca.cab和cba. C语言实现代码如下: /* * Copyright (c) 2011 alexingcool. All Rights Reserved. */ #include <iostream> #include <al

  • c语言生成随机数的方法(获得一组不同的随机数)

    复制代码 代码如下: //以空间换时间/*arr :数组名n  :给定的数组长度转移数组中的数据*/ void diffarr(int arr[],int n){    int sn=n,index,*ptr=arr;    int *pfrom=new int[sn];    for(int i=0;i<sn;i++)        *(pfrom+i) =i+1 ;    srand(time(0));    for(int k=0;k<sn;)    {        index=ran

  • c语言生成随机uuid编码示例

    c语言生成随机uuid编码 复制代码 代码如下: #include <stdio.h>#include <stdlib.h> /** * Create random UUID * * @param buf - buffer to be filled with the uuid string */char *random_uuid( char buf[37] ){    const char *c = "89ab";    char *p = buf;    in

  • C语言实现文件内容按行随机排列的算法示例

    本文实例讲述了C语言实现文件内容按行随机排列的算法.分享给大家供大家参考,具体如下: 在实际工作上有种需求, 就是需要从给定的数据里,随机抽取一部分. 有一种简单的方法是根据总的数据条数和要抽取的数据条数, 通过简单方法,隔几行取一个,这样也能达到随机抽取一部分的目的. 但这样,源数据是顺序的,则抽取的数据也是顺序的,不满足一些情境. 这里实现的功能是: 将全部数据,按行重新随机排列, 这样从结果头部选几行,就是随机抽取的几行了,比较方便. 实现的思路:  对于N行的数据, 给每一行用[1-N]

  • 排列和组合算法的实现方法_C语言经典案例

    排列和组合算法是考查递归的常见算法,这两种算法能用递归简洁地实现. 本人在经过多次摸索和思考之后,总结如下,以供参考. 程序代码如下: #include <stdio.h> #include <stdlib.h> char array[] = "abcd"; #define N 4 #define M 3 int queue[N] = {0}; int top = 0; int flag[N] = {0}; void perm(int s, int n) { i

  • C语言中用于产生随机数的函数使用方法总结

    在UNIX操作系统和window的操作系统上,我们知道有一个函数rand,它就是用来产生随机数的函数API接口,那么它的原理如何实现? 如果约定a1=f(seed),an+1=f(an),那么可以得到一个序列a1,a2,a3..an,那么要制作一个伪随机函数rand,只需要让它每调用一次就返回序列的下一个元素就行.其实就是相当于第1次调用rand返回a1,第2次返回a2,-,第n次返回an,这样每次返回的数值都不一样,也就是相当于随机数了.但是其实不是真正的随机数,真正的随机数是使用物理现象产生

  • C语言/C++如何生成随机数

    本文分享了C语言/C++如何生成随机数的具体实现方法,供大家参考,具体内容如下 C语言/C++怎样产生随机数:这里要用到的是rand()函数, srand()函数,C语言/C++里没有自带的random(int number)函数. (1) 如果你只要产生随机数而不需要设定范围的话,你只要用rand()就可以了:rand()会返回一随机数值, 范围在0至RAND_MAX 间.RAND_MAX定义在stdlib.h, 其值为2147483647. 例如: #include<stdio.h> #i

  • c语言中 基于随机函数的使用详解

    在C语言中,rand()函数可以用来产生随机数,但是这不是真真意义上的随机数,是一个伪随机数,是根据一个数,我们可以称它为种子,为基准以某个递推公式推算出来的一系数,当这系列数很大的时候,就符合正态公布,从而相当于产生了随机数,但这不是真正的随机数,当计算机正常开机后,这个种子的值是定了的,除非你破坏了系统,为了改变这个种子的值,C提供了srand()函数,它的原形是void srand( int a). 可能大家都知道C语言中的随机函数random,可是random函数并不是ANSI C标准,

随机推荐