让我们检查一下程序的执行情况(你可以很容易地做到这一点using a debugger)。
首先因为arr、n和r在调用树中没有变化,我们先把它们记下来:
n := 5, r := 3, arr := {1, 2, 3, 4, 5}
对于变量参数,让我们建立一个表并注意对combinationUtil的第一次调用:
调用栈 |索引 |我 |数据 |案例(打印、退回或重复)
----------+-------+--------+---------- +------------------------------------------------
> 组合Util | 0 | 0 | {., ., .} |递归(索引!= r,我!= n)
打印组合 | | | |
主要 | | | |
让我们在未来忽略main 和printCombination,但请注意,堆栈顶部描述了我们刚刚进入的函数,其状态为index、i 和data(以及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)