有人可以解释递归时如何找到所有子集

Can someone explain how recursion works when finding all subsets?

本文关键字:何找 子集 递归 解释      更新时间:2023-10-16

我一生,图片递归及其在做什么。我为此苦苦挣扎。从竞争激烈的程序员手册中,我在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次,以确保它确实可以完美无缺。但是为什么?

缺乏记住所有递归解决方案的所有问题,我将永远无法提出这种解决方案。这里正在制作什么样的抽象?这里使用的更通用的概念是什么?

我一直在递归中挣扎,因此对任何帮助都得到赞赏。谢谢。

对于每个 k<n 我们只是递归地调用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的二进制树,每个级别代表当前值和两个分支,该值是否进入您的最终集合。叶子代表所有最终组。

so给定 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  ()

AS John 在他的评论中巧妙地指出,递归使用以下事实:

all_subsets(a1,a2,...,an(== all_subsets(a2,...,...,an( u 是设置的联合操作员。

许多其他数学定义将自然地转化为递归调用。

我认为您缺少的是可视化。因此,我建议您访问angorithm-visualizer.org,pythontutor.com等网站。

您可以在此处粘贴此代码片段,然后按行运行它,以便您了解代码流的工作原理。

#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;
}

您真的很想学习。这将有助于您进行竞争性编程。希望这对您有帮助

这不仅是您的问题。每个开始学习递归的人,他/她都会面对这一点。最主要的只是可视化。从字面上看,这很艰难。

如果您尝试通过方便(使用笔和纸(来可视化任何递归代码,则只会看到"哦!,它正在工作"。但是您应该知道,大多数递归都有复发关系。基于此,该函数复发。同样,要查找特定集合的所有子集,有一个复发关系。这是以下...

通过不服用该项目

来服用特定项目

在您的代码中,"采用特定项目"意味着" push_back"answers"不使用特定项目"表示" pop_back"。就是这样。

可能性之一是,不采用任何物品。我们称其为 null Set 。另一种可能性是拿所有物品。这里{0,1,2}。

根据置换组合理论,我们可以计算子集的数量。那就是2 n ,其中n是项目数。这里n = 3。因此,子集的数量将为2 3 = 8。

为0,取它或扔掉,可能性= 2
对于1,取它或扔掉,可能性= 2
对于2,拿或扔掉,可能性= 2

因此,子集的总数为2*2*2 = 8(包括 null Set (。
如果您丢弃 null Set ,那么子集的总数将为8-1 =7。

这是您的递归代码背后的理论。