Python中的递归堆栈

Python中的递归堆栈,python,recursion,callstack,Python,Recursion,Callstack,我试图理解下面python递归函数的调用堆栈。该函数给出给定集合的所有组合 def subsets(A): if not A: yield [] else: for s in subsets(A[1:]): yield s yield [A[0]] + s l1=[1,2,3] print(list(subsets(l1))) 程序正在根据需要生成输出: [[], [1], [2], [1, 2],

我试图理解下面python递归函数的调用堆栈。该函数给出给定集合的所有组合

def subsets(A):
    if not A:
        yield []
    else:
        for s in subsets(A[1:]):
            yield s
            yield [A[0]] + s
l1=[1,2,3]
print(list(subsets(l1)))
程序正在根据需要生成输出:

[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
但是,我无法可视化调用堆栈。如何打印
[1,2,3]
? 我的理解如下

call stack 1 : values are : for s in [2,3], a[0] = 1, a = [1,2,3]
call stack 2 : values are : for s in [3], a[0] = 2, a = [2,3]
call stack 3 : values are : for s in [], a[0] = 3, a = [3]

一些可以帮助我理解带有
s
a
值的调用堆栈流吗?

当您最初调用
susbset
时,参数
a
[1,2,3]
。需要注意的重要一点是,在函数开始为当前参数生成值之前,
[1,2,3]
[3]
[]
,函数将使用参数反复调用自己。然后所有这些递归调用都返回,我们使用
A
执行以下代码,其值为
[1,2,3]

for s in subsets(A[1:]):
    yield s # produces [2, 3]
    yield [A[0]] + s # produces [1, 2, 3]
因此,我们希望生成的最后两个值是包含列表中的
[2,3],[1,2,3]

在生成器上调用
list()
。这将一次又一次地调用发电机,直到发电机耗尽。让我们跟踪执行流程。这是理解生成器的一个很好的练习。我将把所有内容格式化为代码块,以便使用适当的缩进来澄清生成器调用的层次结构

调用子集([1,2,3]),
所以A是[1,2,3]。
此列表不是空的,因此执行else块。
A[1:]是[2,3],因此要确定第一个s,
子集([2,3])被称为。
现在A是[2,3],所以A[1:]是[3],所以要确定s,
称为子集([3])。
现在A是[3],所以A[1:]是[],所以要确定s,
调用子集([])。
现在A是[],因此执行if块。
这将产生[]。
循环以s=[]开始。
这又产生了[]。
现在这个循环开始了,同样是s=[],
因为这就是子集([3])刚刚产生的结果。
所以这也会产生[]。
因此,子集([2,3])产生了[],
所以这个循环也以s=[]开始。
这又一次产生了[]。
因此,子集([1,2,3])产生了[],
现在再次调用此生成器(因为list()),
在先前执行的yield语句之后拾取操作。
所以我们得出下一个结论:收益率[A[0]]+s。
这就产生了[1]。
再次调用子集([1,2,3]),
在通过for循环的第一次运行结束时拾取,
所以要确定下一个s,
再次调用子集([2,3]),
以收益率[A[0]]+s提货。
这就产生了[2]。
因此循环再次开始,s=[2]。
这就产生了[2]。
再次调用子集([1,2,3]),
在收益率[A[0]]+s时提货,s=[2]。
这就产生了[1,2]。
再次调用子集([1,2,3]),
在for循环结束时拾取,
所以要确定下一个s,
再次调用子集([2,3]),
在for循环结束时拾取,
所以要确定下一个s,
再次调用子集([3]),
以收益率[A[0]]+s提货。
这就产生了[3]。
因此循环再次开始,s=[3]。
这就产生了[3]。
因此循环再次开始,s=[3]。
这就产生了[3]。
再次调用子集([1,2,3]),
在收益率[A[0]]+s时提货,s=[3]。
这就产生了[1,3]。
再次调用子集([1,2,3]),
在for循环结束时拾取,
所以要确定下一个s,
再次调用子集([2,3]),
在收益率[A[0]]+s时提货,s=[3]。
这就产生了[2,3]。
因此循环再次开始,s=[2,3]。
这就产生了[2,3]。
再次调用子集([1,2,3]),
收益率[A[0]]+s,s=[2,3]上升。
这就产生了[1,2,3]。
再次调用子集([1,2,3]),
在for循环结束时拾取,
所以要确定下一个s,
再次调用子集([2,3]),
在for循环结束时拾取,
所以要确定下一个s,
再次调用子集([3]),
在for循环结束时拾取,
所以要确定下一个s,
再次调用子集([]),
在if块的末尾拾取,
所以我们到达了发电机的末端,
这意味着它已经耗尽,不再产生任何效果。
所以for循环没有进一步的迭代,
因此,子集([3])也已用尽。
所以子集([2,3])也被耗尽了。
所以子集([1,2,3])也被耗尽了。

这是powerset的直接实现

空的
A
没有子集(
yield[]

对于非空,您有两个选项

  • 保留第一个元素,并连接其余元素的所有可能子集(yield
    [A[0]]+s)

  • 不要保留第一个元素,并返回其余元素的可能子集(
    yield s

  • 因此,使用
    A=[1,2,3]
    就有了
    [1]+子集([2,3])
    子集([2,3])
    的并集。类似地,
    子集([2,3])
    [2]+子集([3])
    子集([3])
    的并集。最后,
    子集([3])
    []
    [3]
    。这给出了3个步骤,每个步骤有2个可能的结果,给出了8种可能的组合。

    试试看