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

时间:2017-08-14 19:03:45

标签: 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))的空列表的列表,这是正确的大小,但数字不在那里。但是,在temp之前打印res.append(temp)表示temp正在输出正确的输出。

E.g。

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)

2 个答案:

答案 0 :(得分:6)

您将对同一列表对象的多个引用追加到result = subsets([1, 2, 3]) print([id(u) for u in result]) 。我们可以通过

来证明这一点
temp

这将打印出8个相同ID的列表。

因此,您对res所做的各种更改会“丢失”,temp的最终内容将是temp的最终值的8个引用,并且这种情况是空列表。

解决此问题的简单方法是将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]]

<强>输出

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

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

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

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

在每个循环中,我们通过循环z并使w中的每个项目与z中的相应项目的副本一起使用当前{x来创建新列表z附加{1}}。然后,我们使用w的内容扩展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]))

只是为了好玩,这里是一个迭代生成器,它从位串生成子集(按自然顺序)。这种方法实际上非常有效,如果你想要一个大序列的所有子集而不消耗大量的RAM,这是很好的。

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

<强>输出

$ cmd="... -more_switches  -mode FOO BAR -mode BAZ -mode BAG -mode CAT DAT HAT -mode RAR"
$ mapfile -t modes < <(grep -oP -- '-mode \K.+?(?= -mode|$)' <<<"$cmd")
$ printf "%s\n" "${modes[@]}"
FOO BAR
BAZ
BAG
CAT DAT HAT
RAR

答案 1 :(得分:1)

变量是对实际值的引用。但是,由于Python列表是可变的,当您通过一个引用更改值时,另一个引用也将反映更改。

brew cask install jce-unlimited-strength-policy

在将其附加到修复程序之前复制一份列表。

>>> a = [1, 3]
>>> b = a
>>> b
[1, 3]
>>> b.append(1)
>>> b
[1, 3, 1]
>>> a
[1, 3, 1]

如其他答案中所述,您也可以使用def subsets(nums): res = [] backtrack(res, [], nums, 0) return res def backtrack(res, temp, nums, start): res.append([]) for i in temp: res[-1].append(i); for i in range(start, len(nums)): temp.append(nums[i]) backtrack(res, temp, nums, i + 1) temp.pop() # Backtrack