我认为使用更长的源列表实际上更容易可视化。如果您使用[0, 1, 2],您会看到递归调用反复切断列表中的最后一项。也就是说,recusion 会构建一堆递归调用,如下所示:
genSubsets([0,1,2])
genSubsets([0,1])
genSubsets([0])
genSubsets([])
在这一点上,它达到了递归算法的“基本情况”。对于此函数,基本情况是作为参数给出的列表为空时。命中基本情况意味着它返回一个包含空列表的列表[[]]。以下是堆栈返回时的样子:
genSubsets([0,1,2])
genSubsets([0,1])
genSubsets([0]) <- gets [[]] returned to it
这样返回值就会回到上一级,并保存在smaller变量中。变量extra被分配为一个切片,只包括列表的最后一项,在这种情况下是整个内容,[0].
现在,循环遍历 中的值smaller,并将它们的连接与extrato相加new。由于smaller(空列表)中只有一个值,因此new最终也只有一个值,[]+[0]即[0]. 我认为这是您在某个时候打印出来的值。
smaller然后最后一条语句返回and的串联new,所以返回值为[[],[0]]。堆栈的另一个视图:
genSubsets([0,1,2])
genSubsets([0,1]) <- gets [[],[0]] returned to it
返回值被smaller再次分配给extrais [1],并且循环再次发生。这一次,new得到两个值,[1]和[0,1]。它们再次连接到末尾,smaller返回值为[[],[0],[1],[0,1]]. 最后一个堆栈视图:
genSubsets([0,1,2]) <- gets [[],[0],[1],[0,1]] returned to it
同样的事情再次发生,这次2在到目前为止找到的每个项目的末尾添加了 s。new结束为[[2],[0,2],[1,2],[0,1,2]].
最终返回值为[[],[0],[1],[0,1],[2],[0,2],[1,2],[0,1,2]]