概述
在算法中,递归是一种解决问题的常见方法,即通过在函数内调用自身来解决问题。
以下是常见的几种递归模式:
基本递归(Binary Recursion)
这是最基本的递归模式,它将计算分为两个部分,然后对每个部分单独进行递归处理。
int binaryRecursion(int n) {
if (n == 0 || n == 1)
return 1;
else
return binaryRecursion(n - 1) + binaryRecursion(n - 2);
}
尾递归(Tail Recursion)
指递归第一件事就是做一个递归调用。
我们可以把这种情况优化成迭代,因为调用不会回来了。
void tailRecursion(int n) {
if (n == 0) return;
cout << n << endl;
tailRecursion(n - 1); // Tail call
}
维基百科尾递归:
若函数在尾位置调用自身(或是一个尾调用本身的其他函数等等),则称这种情况为尾递归。尾递归也是递归的一种特殊情形。尾递归是一种特殊的尾调用,即在尾部直接调用自身的递归函数。对尾递归的优化也是关注尾调用的主要原因。尾调用不一定是递归调用,但是尾递归特别有用,也比较容易实现。
特点:
尾递归在普通尾调用的基础上,多出了2个特征:
- 在尾部调用的是函数自身 (Self-called);
- 可通过优化,使得计算仅占用常量栈空间 (Stack Space)。
头递归
头递归函数在调用自身之后,存在后续操作,而尾递归则在函数的最后一步调用自身。
// Head Recurison
int factorial(int n)
{
if (n==1)
{
return 1;
}
return factorial(n-1)*n;
}
回溯(Backtracking)
常常用于寻找所有解决方案的问题,在每次递归时创建并向搜索树添加新的节点。
当遇到满足条件的结果时,将结果存储下来;
如果不符合条件,则返回上一个节点并从集成的节点中删除它。
void backtracking(string comb, string digits, vector<string>& res, const vector<string>& v) {
if (digits.empty()) { // 找到一个正确的匹配组合
res.push_back(comb);
return;
}
char digit = digits[0];
string letters = v[digit - '0'];
for (int i = 0; i < letters.size(); ++i) {
backtracking(comb + letters[i], digits.substr(1), res, v);
}
}
分治(Divide and Conquer)
将问题分成许多子问题,并将每个子问题的求解结果合并起来得到整个问题的解决办法。
vector<int> divideConquer(vector<int>& nums, int left, int right) {
if (left == right) // 如果只有一个元素则直接返回
return {nums[left]};
vector<int> res;
// 分割数组,对左右两部分分别使用divideConquer方法
int mid = left + (right - left) / 2;
vector<int> leftVals = divideConquer(nums, left, mid);
vector<int> rightVals = divideConquer(nums, mid + 1, right);
// 合并左右两部分的结果
int i = 0, j = 0;
while (i < leftVals.size() && j < rightVals.size()) {
if (leftVals[i] < rightVals[j]) {
res.push_back(leftVals[i++]);
} else {
res.push_back(rightVals[j++]);
}
}
while (i < leftVals.size()) {
res.push_back(leftVals[i++]);
}
while (j < rightVals.size()) {
res.push_back(rightVals[j++]);
}
return res;
}