如何生成升序序列
一个由3个数字组成的序列:ABC,0<=A<=B<=C<=9。问:总共有多少种组合?
首先,如果不考虑 A<=B<=C,则总共有10*10*10=1000种组合,即000-999。 我们可以根据排列组合的方式来解决这个问题,考虑ABC,3个数字是否相等来讨论,比如3个数字一样时,即如下:
a) AAA的形式,总共有10种情况。
b) AAB或ABB的形式有:C(10, 2) + C(10, 2) = 45 + 45 = 90种。
c) ABC的形式有 C(10, 3) = 10 * 9 * 8 / 6 = 120种
总计有220种组合。
利用程序实现如下:
// 3个循环 #include <iostream> using namespace std; int main() { int total = 0; for (int A = 0; A != 10; ++A) { for (int B = A; B != 10; ++B) { for (int C = B; C != 10; ++C) { ++total; } } } cout << total << endl; system("PAUSE"); return 0; }
针对有3个数字组成的组合,我们的程序可以利用3个循环来得到所有的组合数。但是如果有N个数字组成的组合该如何解决呢?是否要用N个循环呢?显然,使用N个循环只能处理特定的N个数字的情况,不能处理N为变量的情形。
我们很自然就想到了能不能利用递归方法来实现。
程序如下:
// 递归实现 #include <iostream> #include <vector> using namespace std; void foo(int start, int end, int x, int n, int& total) { if (x > n) { total = 0; return; } for (int i = start; i <= end; ++i) { if (x == n) { ++total; } else { foo(i, end, x+1, n, total); } } } int main() { int total = 0; foo(0, 9, 1, 1, total); cout << total << endl;; total = 0; foo(0, 9, 1, 2, total); cout << total << endl;; total = 0; foo(0, 9, 1, 3, total); cout << total << endl;; system("PAUSE"); return 0; }
这个递归程序得到了N个数字的组合数,如果我们想让其输出具体的组合有哪些该怎么办呢?
1) 尝试I
// 输出具体的组合 #include <iostream> #include <vector> using namespace std; void foo(int start, int end, int x, int n, int& total) { if (x > n) { total = 0; return; } for (int i = start; i <= end; ++i) { if (x == n) { cout << i << endl; ++total; } else { cout << i << ' '; foo(i, end, x+1, n, total); } } } int main() { int total = 0; foo(0, 9, 1, 3, total); cout << total << endl;; system("PAUSE"); return 0; }
从运行结果可以看到,我们得到是程序运行的过程状态。我们对最后面四行结果进行解释:
8 8 8
9
9 9
9 9 9
实际的序列式:888、889、899、999,程序输出的结果并没有把“9”之前的前缀“88”、“99”之前的前缀“8”输出,所以才会产生如上的结果形式。
整个递归过程是一个树的深度遍历过程,这是一个回溯操作。为了能够得到清晰的结果组合,我们使用一个栈来记录遍历树的过程中的节点,当遍历到最底层时,我们就将该栈收集起来,如果是中间节点,我们先压栈,然后继续遍历,在回溯的时候出栈。
程序实现如下:
// 得到组合明细 #include <iostream> #include <vector> using namespace std; void foo(int start, int end, int x, int n, int& total, vector<vector<int> >& vv, vector<int>& v) { for (int i = start; i <= end; ++i) { if (x == n) { v.push_back(i); // cout << i << endl; ++total; vv.push_back(v); v.pop_back(); } else { v.push_back(i); // cout << i << ' '; foo(i, end, x+1, n, total, vv, v); v.pop_back(); } } } int main() { int total = 0; vector<vector<int> > vv; vector<int> v; foo(0, 9, 1, 3, total, vv, v); for (vector<vector<int> >::size_type i = 0; i != vv.size(); ++i) { for (vector<int>::size_type j = 0; j != vv[i].size(); ++j) { cout << vv[i][j]; } cout << endl; } cout << endl << total << endl;; cout << vv.size() << endl; cout << v.size() << endl; system("PAUSE"); return 0; }
// 如果N=10 #include <iostream> #include <vector> using namespace std; void foo(int start, int end, int x, int n, int& total, vector<vector<int> >& vv, vector<int>& v) { for (int i = start; i <= end; ++i) { if (x == n) { v.push_back(i); // cout << i << endl; ++total; vv.push_back(v); v.pop_back(); } else { v.push_back(i); // cout << i << ' '; foo(i, end, x+1, n, total, vv, v); v.pop_back(); } } } int main() { int total = 0; vector<vector<int> > vv; vector<int> v; foo(0, 9, 1, 10, total, vv, v); /* for (vector<vector<int> >::size_type i = 0; i != vv.size(); ++i) { for (vector<int>::size_type j = 0; j != vv[i].size(); ++j) { cout << vv[i][j]; } cout << endl; } */ cout << endl << total << endl;; cout << vv.size() << endl; cout << v.size() << endl; total = 0; vv.clear(); v.clear(); foo(0, 9, 1, 11, total, vv, v); cout << endl << total << endl;; cout << vv.size() << endl; cout << v.size() << endl; system("PAUSE"); return 0; }
总结:
递归的实现关键点在于借口的定义:
void foo (int start, int end, int x, int n, int& total, vector<vector<int> >& vv, vector<int>& v)
[start, end] 为 第 x 个数字的取值范围;
x 为第几个数字
n 为总共几个数字
total 记录总的组合数
vv 记录组合明细
v 记录遍历过程中的组合
另一方面,是for循环:for (int i = start; i <= end; ++i),一开始就判断x是否等于n,以检测是否到了树叶子节点,或者对于N=1的情况是否有效。如果x不等于n则进入递归调用,由于后面的数字大于等于前面的数字,所以第一个调用参数为i,第3个参数变为x+1。所以,递归函数中的递归调用也是个关键点。
最后,在实际调用种需要注意的一点是x的取值,根据我们的实现,x的取值应该为1。如果更改递归函数的实现,或许可以设定x的取值为0,这里不再作讨论。
附:如果不考虑A<=B<=C这个因素,则组合数为1000。程序实现如下:
// 不考虑A<=B<=C #include <iostream> #include <vector> using namespace std; void foo(int start, int end, int x, int n, int& total, vector<vector<int> >& vv, vector<int>& v) { for (int i = start; i <= end; ++i) { if (x == n) { v.push_back(i); // cout << i << endl; ++total; vv.push_back(v); v.pop_back(); } else { v.push_back(i); // cout << i << ' '; foo(start, end, x+1, n, total, vv, v); v.pop_back(); } } } int main() { int total = 0; vector<vector<int> > vv; vector<int> v; foo(0, 9, 1, 3, total, vv, v); for (vector<vector<int> >::size_type i = 0; i != vv.size(); ++i) { for (vector<int>::size_type j = 0; j != vv[i].size(); ++j) { cout << vv[i][j]; } cout << endl; } cout << endl << total << endl;; cout << vv.size() << endl; cout << v.size() << endl; system("PAUSE"); return 0; }