尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手。由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论。以在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; } } }
1 2 3 4 2 3 4 3 4
下面是非递归的回溯方法的实现:
/// 求从数组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; } } }
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; }