什么样的问题可以用递归?
其实很多问题都可以用递归解决,例如数列的求和:
using namespace std;
template <class T>
T recrusive_sum(T a[], int idx)
{
if(idx == 0) // 退出条件
return a[0];
else
return a[idx] + recrusive_sum(a, idx - 1); // 使用内层返回的结果
}
int main(void)
{
const int elem_cnt = 100;
int a[elem_cnt];
for(int i = 0; i < elem_cnt; ++i)
a[i] = i + 1;
cout << recrusive_sum(a, elem_cnt - 1) << endl;
return 0;
}
显然,这不是一个高效的算法,我们通常用率更高的迭代法来解决上面的问题。用这个例子只是想说明,很多问题可以用递归解决。
能用递归解决的问题通常具有两个特点:
1 有退出条件
2 外层需要用到内层算出的结果(也可能是内层需要外层的计算结果,但比较少见)
最难的地方是找出外层利用内层结果的方法,这往往需要在思考问题的过程中发现规律,纸笔是不可缺少的。
另外退出条件需要拿捏准确,这也是一个容易出错的地方。
下面是求全排列和求全部子集的算法,注意以上两点在代码中的体现。
(*) 求全排列
不妨写出一个简单例子,我们用P(a,b,c)表示a,b,c的全排列,
则P(a,b,c)=
a b c
a c b
b a c
b c a
c a b
c b a
我们发现,以上结果按首字母可划分为三组,它们是
a + P(b,c)
b + P(a,c)
c + P(a,b)
其实就是第一个字母轮换,其余两个位置是剩下两个字母的全排列。“剩下两个字母的全排列”正是我们可以利用的内层结果。代码如下:
using namespace std;
template <class T>
void perm(T list[], int begin, int end)
{
if(begin == end){
for(int i = 0; i <= end; ++i)
cout << list[i] << '\t';
cout << endl;
}
else{
for(int i = begin; i <= end; ++i){
swap(list[begin], list[i]);
perm(list, begin + 1, end);
swap(list[begin], list[i]);
}
}
}
int main()
{
char a[] = {'1', '2', '3', '4'};
int min_idx = 0;
int max_idx = sizeof a / sizeof *a - 1;
perm(a, min_idx, max_idx);
return 0;
}
(*) 求所有子集
不妨写一个简单的例子,然后从中发现规律。例如,集合{a,b,c}的子集有:
{}
{a}
{b}
{a, b}
{c}
{a, c}
{b, c}
{a, b, c}
耐心分析结果,发现:
{} (0)
{a} = {a} + {} (a)
{b} = {b} + {} (b)
{a, b} = {b} + {} + {a} (b)
{c} = {c} + {} (c)
{a, c} = {c} + {a} (c)
{b, c} = {c} + {b} (c)
{a, b, c} = {c} + {a, b} (c)
其实我们能从上面的分析过程中得到不只一条结论:
1 所有的子集总数是二项展开式系数和C(n,0)+C(n,1)+...+C(n,n)=2^n.这个结论虽然对解决本题没什么帮助,但它应该,也是最容易被注意到的。
2 我们将上面的所有子集分组,发现从最简单的空集开始,新出现的组都是新元素和之前所有组的笛卡尔积。这正是递归利用内层计算结果的地方。代码如下:
#include <vector>
#include <string>
using namespace std;
template <class T>
void subset(vector< vector<T> >& res, const vector<T>& src, int idx);
template <class T>
void show_1d(const vector<T>& vec);
template <class T>
void show_2d(const vector< vector<T> >& vec);
template <class T>
void append_cartesian(vector< vector<T> >& res, T appendant);
int main()
{
vector<string> src;
vector< vector<string> > res;
src.push_back("c");
src.push_back("b");
src.push_back("a");
subset(res, src, 0);
show_2d(res);
return 0;
}
template <class T>
void subset(vector< vector<T> >& res, const vector<T>& src, int idx)
{
if(src.size() == idx){
vector<T> empty_set;
res.push_back(empty_set);// append an empty vector when reach base
}
else{
subset(res, src, idx + 1);
append_cartesian(res, src[idx]);//将内层算出的结果笛卡尔积到本层
}
}
template <class T>
void append_cartesian(vector< vector<T> >& res, T appendant)
{
int len = res.size();
for(int i = 0; i < len; ++i){
vector<T> tmp = res[i];
tmp.push_back(appendant);
res.push_back(tmp);
}
}
template <class T>
void show_1d(const vector<T>& vec)
{
cout << '{';
for(int i = 0; i < vec.size(); ++i){
cout << vec[i];
if(i != vec.size() - 1)
cout << ", ";
}
cout << '}' << endl;
}
template <class T>
void show_2d(const vector< vector<T> >& vec)
{
for(int i = 0; i < vec.size(); ++i)
show_1d(vec[i]);
}