python - 使用递归回溯生成集合的所有子集 (Python)

标签 python recursion permutation backtracking recursive-backtracking

我试图理解回溯,但我陷入了这个问题,这是提示:

给定一组不同的整数,返回所有可能的子集。

示例输入:[1,2,3]

示例输出:[[], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3] ]

这是我的代码:

def subsets(nums):
    res = []
    backtrack(res, [], nums, 0)
    return res

def backtrack(res, temp, nums, start):
    # print(temp)
    res.append(temp)
    for i in range(start, len(nums)):
        temp.append(nums[i])
        backtrack(res, temp, nums, i + 1)
        temp.pop() # Backtrack

当我返回 res 时,我得到一个大小为 2^(len(nums)) 的空列表列表,这是正确的大小,但数字不正确那里。但是,在执行 res.append(temp) 之前打印 temp 显示 temp 正在执行正确的输出。

例如

res = [[], [], [], [], [], [], [], []]

打印语句:

[] [1] [1, 2] [1, 2, 3] [1, 3] [2] [2, 3] [3]

为什么更改没有转移到 res 列表中?

编辑 1:

这个解决方案有效,有什么区别?

def subsets(nums):
    res = []
    backtrack(res, [], nums, 0)
    return res

def backtrack(res, temp, nums, start):
    # print(temp)
    res.append(temp)
    for i in range(start, len(nums)):
        backtrack(res, temp + [nums[i]], nums, i + 1)

最佳答案

您将对同一列表对象的多个引用附加到 res。我们可以通过做来证明这一点

result = subsets([1, 2, 3])
print([id(u) for u in result])

这将打印一个包含 8 个相同 ID 的列表。

因此,您对 temp 所做的各种更改将“丢失”,而 res 的最终内容将是对 最终值的 8 个引用temp 是,在本例中它是空列表。


解决此问题的简单方法是将 temp 的副本附加到 res

def subsets(nums):
    res = []
    backtrack(res, [], nums, 0)
    return res

def backtrack(res, temp, nums, start):
    res.append(temp[:])
    for i in range(start, len(nums)):
        temp.append(nums[i])
        backtrack(res, temp, nums, i + 1)
        temp.pop() # Backtrack

print(subsets([1, 2, 3]))

输出

[[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]

FWIW,我意识到这个练习的主要目的是练习递归,但在 Python 中最好避免递归,除非你真的需要它(例如,用于处理像树这样的递归数据结构)。但这里有一个更紧凑的迭代解决方案。

def subsets(seq):
    z = [[]]
    for x in seq:
        z += [y + [x] for y in z]
    return z

要查看它是如何工作的,我们可以稍微扩展它,并添加一个 print 调用。

def subsets(seq):
    z = [[]]
    for x in seq:
        print('z =', z, 'x =', x)
        w = []
        for y in z:
            w += [y + [x]]
        z += w
    return z

result = subsets([1, 2, 3])
print(result)  

输出

z = [[]] x = 1
z = [[], [1]] x = 2
z = [[], [1], [2], [1, 2]] x = 3
[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]

我们从包含单个空列表的列表 z 开始。

在每个循环中,我们通过遍历 z 并使 w 中的每个项目成为相应项目的副本来创建一个新列表 w z 加上当前的 x。然后我们用 w 的内容扩展 z


只是为了好玩,这里有一个迭代生成器,它从位串中生成子集(以自然顺序)。这种方法实际上非常有效,如果您希望在不消耗大量 RAM 的情况下获取大型序列的所有子集,这种方法非常有用。

def subsets(seq):
    w = len(seq)
    for i in range(1<<w):
        yield [u for u, v in zip(seq, reversed('{:0{}b}'.format(i, w))) if v=='1']

print(*subsets([1, 2, 3]))

输出

[] [1] [2] [1, 2] [3] [1, 3] [2, 3] [1, 2, 3]

关于python - 使用递归回溯生成集合的所有子集 (Python),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45681354/

相关文章:

python - Python 中 boolean 表达式的求值

java - 如何引用不同目录子结构中的文件/目录?

python - python 中的优化方法。类似于排列

python - GEE不将数据导入数组

python - SQLAlchemy 线程池执行器 "Too many clients"

C - 忽略子目录名,只打印文件名

C++ 递归函数中的平衡树/调用顺序

python - 给定一个数字列表,你可以用多少种不同的方式将它们相加得到总和 S?

c# - 使用通配符生成单词的所有排列

python - 尝试安装 scipy 时出错