对于我的一生,我无法进行图片递归及其操作。我为此付出了很多努力。从《竞争程序员手册》 中,我发现了以下C ++代码片段,以解决以下问题:
请考虑生成一组n个元素的所有子集的问题。 例如,{0,1,2}的子集为;,{0},{1},{2},{0,1}, {0,2},{1,2}和{0,1,2}。
遍历集合的所有子集的一种优雅方法是使用递归。 以下函数搜索生成集合的子集 {0,1,...,n − 1}。该函数维护一个向量子集,该子集将 包含每个子集的元素。搜索开始于 该函数使用参数0调用。
使用参数k调用函数搜索时,它决定 是否在子集中以及在两个子集中都包含元素k 情况下,则使用参数k + 1调用自身。但是,如果k = n,则 函数会注意到所有元素都已处理,并且有一个子集 已生成。
void search(int k) {
if (k == n) {
// process subset
} else {
search(k+1);
subset.push_back(k);
search(k+1);
subset.pop_back();
}
}
所以可以肯定,此功能有效,我手工完成了大约3次,以确保它确实可以正常工作。但是为什么呢?
记住所有问题的所有递归解决方案的时间很短,我将永远无法提出这种解决方案。在这里进行什么样的抽象?在这里使用的更一般的概念是什么?
我一直在为递归而苦苦挣扎,因此我们将不胜感激。谢谢。
答案 0 :(得分:4)
对于每个 k search(k+1)
。一次在您的集合中包含值 k ,一次不包含它。
search(k+1); // call search (k+1) with k NOT inside the set
subset.push_back(k); // puts the value k inside the set
search(k+1); // call search (k+1) with k inside the set
subset.pop_back(); // removes the value k from the set
一旦达到 n == k ,递归就会终止。
想象一个深度为n的二叉树,其中每个级别代表当前值和两个分支,确定该值是否进入最终集合。叶子代表所有最终集合。
因此,给定 n = 3 并从 k = 0 开始,您将得到:
search(0);
-> search(1); // with 0 in
->-> search(2); // with 0 in AND 1 in
->->-> search (3); // with 0 in AND 1 in AND 2 in. terminates with (0,1,2)
->->-> search (3); // with 0 in AND 1 in AND 2 not in. terminates with (0,1)
->-> search(2); // with 0 in AND 1 not in
->->-> search (3); // with 0 in AND 1 not in AND 2 in. terminates with (0,2)
->->-> search (3); // with 0 in AND 1 not in AND 2 not in. terminates with (0)
-> search(1); // with 0 not in
->-> search(2); // with 0 not in AND 1 in
->->-> search (3); // with 0 not in AND 1 in AND 2 in. terminates with (1,2)
->->-> search (3); // with 0 not in AND 1 in AND 2 not in. terminates with (1)
->-> search(2); // with 0 not in AND 1 not in
->->-> search (3); // with 0 not in AND 1 not in AND 2 in. terminates with (2)
->->-> search (3); // with 0 not in AND 1 not in AND 2 not in. terminates with ()
john 在其评论中巧妙地指出,递归使用以下事实:
all_subsets(a1,a2,...,an)== all_subsets(a2,...,an)U {a1,all_subsets(a2,...,an)} 其中 U 是集合联合运算符。
许多其他数学定义自然会转换为递归调用。
答案 1 :(得分:2)
我认为您所缺少的是可视化。因此,我建议您访问algorithm-visualizer.org,pythontutor.com之类的网站。
您可以粘贴此代码段here并逐行运行它,以便您了解代码流的工作方式。
#include <bits/stdc++.h>
using namespace std;
void subsetsUtil(vector<int>& A, vector<vector<int> >& res, vector<int>& subset, int index) {
res.push_back(subset);
for (int i = index; i < A.size(); i++) {
subset.push_back(A[i]);
subsetsUtil(A, res, subset, i + 1);
}
return;
}
vector<vector<int> > subsets(vector<int>& A) {
vector<int> subset;
vector<vector<int> > res;
int index = 0;
subsetsUtil(A, res, subset, index);
return res;
}
int32_t main() {
vector<int> array = { 1, 2, 3 };
vector<vector<int> > res = subsets(array);
for (int i = 0; i < res.size(); i++) {
for (int j = 0; j < res[i].size(); j++)
cout << res[i][j] << " ";
cout << endl;
}
return 0;
}
您真正想学习的很好。这将对您进行竞争性编程大有帮助。希望对您有帮助
答案 2 :(得分:0)
这不仅是您的问题。第一次开始学习递归的每个人都将面对这一点。最主要的只是可视化。从字面上看,这很难。
如果您尝试通过使其方便(使用笔和纸)来可视化任何递归代码,则只会看到“哦!它正在工作”。但是您应该知道大多数递归都具有递归关系。基于此,函数再次出现。类似地,为了找到特定集合的所有子集,存在一个递归关系。以下是...
通过服用特定物品+不服用该物品
在您的代码中,“接受特定项目”表示“推回”,而“不接受特定项目”则表示“ Pop_back”。就是这样。
一种可能性是不带物品。我们称其为空集。 另一种可能性是,拿走所有物品。这里{0,1,2}。
根据置换组合理论,我们可以计算子集的数量。那是2 n ,其中n是项数。在此,n = 3。因此子集的数量将是2 3 = 8。
对于0,采用或抛出,可能性= 2
对于1,拿走或扔掉,可能性= 2
对于2,拿走或扔掉,可能性= 2
因此,子集总数为2 * 2 * 2 = 8(包括空集)。
如果丢弃空集,那么子集的总数将为8-1 = 7。
这就是您的递归代码的原理。