当前位置: 首页 > 编程笔记 >

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

万勇
2023-03-14
本文向大家介绍C语言实现的排列组合问题的通用算法、解决方法,包括了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求出的排列是按排列从小到大的顺序进行的。

 类似资料:
  • 本文向大家介绍字符串的组合算法问题的C语言实现攻略,包括了字符串的组合算法问题的C语言实现攻略的使用技巧和注意事项,需要的朋友参考一下 基本字符串组合问题 题目:输入一个字符串,输出该字符串中字符的所有组合。举个例子,如果输入abc,它的组合有a、b、c、ab、ac、bc、abc。 上面我们详细讨论了如何用递归的思路求字符串的排列。同样,本题也可以用递归的思路来求字符串的组合。 假设我们想在长度为

  • 本文向大家介绍C语言实现最长递增子序列问题的解决方法,包括了C语言实现最长递增子序列问题的解决方法的使用技巧和注意事项,需要的朋友参考一下 本文实例展示了C语言实现最长递增子序列问题的解决方法。分享给大家供大家参考。具体方法如下: 问题描述: 给定一个序列,找出其最长递增子序列长度。 比如 输入 1 3 7 5 输出 3 算法解决思路: 利用动态规划的思想,以序列的每个点最为最右端,找出每个点作为

  • 本文向大家介绍使用C语言来解决循环队列问题的方法,包括了使用C语言来解决循环队列问题的方法的使用技巧和注意事项,需要的朋友参考一下 题目描述:     大家都知道数据结构里面有一个结构叫做循环队列。顾名思义,这是一个队列,并且是循环的。但是现在,淘气的囧哥给这个循环队列加上了一些规矩,其中有5条指令:     (1) Push K, 让元素K进队列。     (2) Pop,对头元素出队列。   

  • 本文向大家介绍C语言基于回溯算法解决八皇后问题的方法,包括了C语言基于回溯算法解决八皇后问题的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了C语言基于回溯算法解决八皇后问题的方法。分享给大家供大家参考,具体如下: 问题描述: 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例:在8X8格的国际象棋棋盘上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一

  • 本文向大家介绍C语言实现冒泡排序算法,包括了C语言实现冒泡排序算法的使用技巧和注意事项,需要的朋友参考一下 BubblSort.c 以上所述就是本文的全部内容了,希望对大家学习C语言能够有所帮助。

  • 本文向大家介绍C#通过yield实现数组全排列的方法,包括了C#通过yield实现数组全排列的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了C#通过yield实现数组全排列的方法。分享给大家供大家参考。具体分析如下: 从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。 希望本文所述对大家的C#