【问题标题】:Program to print all subset of length l打印长度为 l 的所有子集的程序
【发布时间】:2020-07-18 15:37:02
【问题描述】:
#include<iostream>
using namespace std;
void combinationUtil(int arr[], int n, int r, int index, int data[], int i);
void printCombination(int arr[], int n, int r)
{
    int data[r];
    combinationUtil(arr, n, r, 0, data, 0);
}
void combinationUtil(int arr[], int n, int r, int index, int data[], int i)
{
    if (index == r)
    {
        for (int j = 0; j < r; j++)
            cout << data[j] << " ";
        cout << endl;
        return;
    }
    if (i >= n)
        return;
    data[index] = arr[i];
    combinationUtil(arr, n, r, index + 1, data, i + 1);
    combinationUtil(arr, n, r, index, data, i + 1);
}
int main()
{
    int n = 5;
    int arr[n] = { 1, 2, 3, 4, 5 };
    int k = 3;
    printCombination(arr, n, k);
    return 0;
}

这是打印长度为 k 的所有可能子集的代码。我不明白在打印子集125之后函数如何返回打印子集134的部分。请解释一下。 我什至画了树,但是代码在打印 125 之后如何创建“13”系列。我在递归方面很弱,如果我的代码或树中有错误,请纠正我。

递归树:

【问题讨论】:

  • “打印子集 125 后打印子集 134 的部分”:那是什么部分?
  • 你能解释一下combinationUtil的各种参数代表什么吗?
  • 你已经绘制了递归树,你在纸上得到了你在屏幕上得到的东西。那有什么问题呢?您是否对如何绘制那棵树感到困惑?
  • 在遍历绘制的树时尝试记下调用堆栈和局部变量的状态。你能看出你的电话是如何运作的,以及你是如何绘制你的树的吗? IE。您绘制的树并不是您的代码执行方式(提示:您的调用结构中的每个内部节点只有两个子节点,但您绘制的树每个节点最多包含三个子节点)。

标签: c++ recursion subset


【解决方案1】:

让我们检查一下程序的执行情况(你可以很容易地做到这一点using a debugger)。

首先因为arrnr在调用树中没有变化,我们先把它们记下来:

n := 5, r := 3, arr := {1, 2, 3, 4, 5}

对于变量参数,让我们建立一个表并注意对combinationUtil的第一次调用:

调用栈 |索引 |我 |数据 |案例(打印、退回或重复) ----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 0 | 0 | {., ., .} |递归(索引!= r,我!= n) 打印组合 | | | | 主要 | | | |

让我们在未来忽略mainprintCombination,但请注意,堆栈顶部描述了我们刚刚进入的函数,其状态为indexidata(以及arr, n, r 但这些不会改变)——特别是我们还没有执行函数的任何主体,比如分配 data[index] = arr[i] 或类似的。在执行说明之后,自index != r and i != n 以来我们既没有打印也没有提前中断,这意味着我们将在这个函数调用中递归两次。让我们进入第一个递归调用:

----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 1 | 1 | {1, ., .} |递归的 组合工具 | 0 | 0 | |

当我们进入第一个递归调用时,1 将被写入data,所以在函数的开头,我们现在可以在data 中看到这个值。我们将继续两次:

----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 2 | 2 | {1, 2, .} |递归的 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | | ----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 3 | 3 | {1, 2, 3} |打印(索引 == r) 组合工具 | 2 | 2 | | 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | |

新案例:index == r 即我们将循环 data,打印其当前内容并返回,上一层我们将进入下一个递归调用,所以让我们看看接下来的步骤:

----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 2 | 3 | {1, 2, 3} |递归的 组合工具 | 2 | 2 | | 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | | ----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 3 | 4 | {1, 2, 4} |印刷 组合工具 | 2 | 3 | | 组合工具 | 2 | 2 | | 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | | ----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 2 | 4 | {1, 2, 4} |递归的 组合工具 | 2 | 3 | | 组合工具 | 2 | 2 | | 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | | ----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 3 | 5 | {1, 2, 5} |印刷 组合工具 | 2 | 4 | | 组合工具 | 2 | 3 | | 组合工具 | 2 | 2 | | 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | | ----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 2 | 5 | {1, 2, 5} |返回 (i >= n) 组合工具 | 2 | 4 | | 组合工具 | 2 | 3 | | 组合工具 | 2 | 2 | | 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | |

在我们打印125 之前,您能看到调用堆栈有多深吗?如果您一直在绘制调用图,那么您现在应该有以下内容:

[] (0, 0) / 1 (1, 1) / 12 (2, 2) / \ 123 12 (2, 3) / \ 124 12 (2, 4) / \ 125 ret (2, 5)

我们目前位于ret,并且我已经在根路径(从[]ret)的当前调用堆栈中标记了参数(index, i)。我们将返回ret,因为i == n,然后我们将从每个调用树返回,直到我们到达1 (index=1, i=1),我们从它的第一次递归(index+1, i+1)返回,所以它的下一个是(index, i+1)和接下来的 6 个步骤如下所示:

----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 1 | 2 | {1, 2, 5} |递归的 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | | ----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 2 | 3 | {1, 3, 5} |递归的 组合工具 | 1 | 2 | | 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | | ----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 3 | 4 | {1, 3, 4} |印刷 组合工具 | 2 | 3 | | 组合工具 | 1 | 2 | | 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | | ----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 2 | 4 | {1, 3, 4} |递归的 组合工具 | 2 | 3 | | 组合工具 | 1 | 2 | | 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | | ----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 3 | 5 | {1, 3, 5} |印刷 组合工具 | 2 | 4 | | 组合工具 | 2 | 3 | | 组合工具 | 1 | 2 | | 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | | ----------+-------+--------+---------- +------------------------------------------------ > 组合Util | 2 | 5 | {1, 3, 5} |返回 组合工具 | 2 | 4 | | 组合工具 | 2 | 3 | | 组合工具 | 1 | 2 | | 组合工具 | 1 | 1 | | 组合工具 | 0 | 0 | |

到目前为止的调用图如下所示:

[] (0, 0) / 1 (1, 1) /--------^--------\ 12 1 (1, 2) / \ / 123 12 13 (2, 3) / \ / \ 124 12 134 13 (2, 4) / \ / \ 125 ret 135 ret (2, 5)

【讨论】:

  • ``` [] (0, 0) / 1 (1, 2) / \ 13 (2, 3) / \ 134 13 (2, 4) / \ 135 ret (2, 5 ) ```@BeylerStudios
  • +1 @VinayakaDayanand 几乎,你只是错过了[] (0, 0)1 (1, 2) 之间的1 (1, 1),我现在会更新我的答案。
【解决方案2】:

您的代码是 DFS(深度优先搜索)的示例。对于每个节点,它首先在最左边的子节点上递归,然后当它在最右边的子节点上时,函数的这个“部分”结束,本质上是回到前一个节点并在其子节点上循环。基本上,孩子优先于兄弟姐妹,左优先于右。

如果这篇文章的任何部分不清楚,请告诉我,我会详细说明。欢迎来到堆栈溢出 :)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-04
    • 2021-12-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多