目录
DFS或BFS解法
这个问题很简单,对于给定的集合,输出所有的子集合。对于这个问题,我们不用学习组合数学也知道这是2的n次方种子集合,特殊的有空集和平凡子集(也就是本身)。
以三个元素的集合为例子,我们可以把所有子集合化成一张图
上图中,N代表空的意思。也就是对于集合的每个元素,我们选择要这个元素还是不要这个元素。很明显,这是一个树状结构。对于树状结构,我们有两种算法,DFS与BFS。如果我们只用一个路径变量,很难存储每个结点从根部到自己的完整路径,所以采用节点本身存储自身到根部的路径的策略。Null是不能删除的,因为通过NULL凑数,就可以通过节点本身的路径长度判断节点自身所处的树层级,方便取子节点或结束寻找子节点过程。
思路找到了,代码写起来就非常容易了,以下是DFS算法的python代码:
def from_path(path):
result = []
for e in path:
if e is not None:
result.append(e)
return result
# all subsets of set
def subsets(input):
# DFS用的栈
stack = [[None]]
# 路径
# 所有子集合
n = len(input)
all_subsets = []
while len(stack) > 0:
node = stack.pop()
level = len(node)
if level == n + 1:
all_subsets.append(from_path(node))
else:
# add children
stack.append(node + [None])
stack.append(node + [input[level - 1]])
return all_subsets
if __name__ == '__main__':
print(subsets([1, 2, 3]))
输出结果:
[[1, 2, 3], [1, 2], [1, 3], [1], [2, 3], [2], [3], []]
二项组合树解法
前文已经介绍过,链接如下:
但是代码有所不同,因为这里不能用剪枝策略。
def combinations(input):
# 队列元素是集合加上索引的元组
queue = [([], 0)]
result = []
while len(queue) > 0:
e = queue.pop(0)
result.append(e[0])
# 计算children
subarray = input[e[1]:]
for i, child in enumerate(subarray):
x = (e[0] + [child], e[1] + i + 1)
queue.append(x)
return result
if __name__ == '__main__':
print(combinations(['A', 'B', 'C']))
测试结果:
[[], ['A'], ['B'], ['C'], ['A', 'B'], ['A', 'C'], ['B', 'C'], ['A', 'B', 'C']]
位运算解法
我们考虑到每一个元素在不在子集合中,可以组成一个二进制的一位。所以N个元素的子集合就可以用二进制数字表示。如此可以避免频繁出栈入栈,不仅代码性能更高,而且更加简短清晰。
def from_mask(input, mask):
result = []
for i, e in enumerate(input):
if (1 << i) & mask != 0:
result.append(e)
return result
# all subsets of set
def subsets(input):
# DFS用的栈
# 路径
# 所有子集合
n = len(input)
all_subsets = [from_mask(input, mask) for mask in range(0, 1 << n)]
return all_subsets
if __name__ == '__main__':
print(subsets([1, 2, 3]))
输出结果
[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]