两种求幂集的方法

今天这一题是求幂集。小学数学都忘得差不多了… 幂集是什么?

[blockquote]

幂集(power set)是一个集合的所有子集。比如[1, 2, 3]的幂集就是:

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

[/blockquote]

不过这道题有一个额外的要求:

[blockquote]

在求幂集时要以“深度优先搜索”(depth-first search)的形式进行。也就是说,对于集合的每个元素,都有“取”和“舍”两种选择;在深度优先时,我们优先选择“舍”。仍以[1, 2, 3]为例,在这种情况下,它的幂集应该是:

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

现在给定一个列表,如何以深度优先遍历的方法找出它的幂集?

[/blockquote]

这道题有两种思路。第一种,是采用回溯法。用白话说,就是在每一个分叉路口,偏执的选择一条道走到黑,此时再返回至上一个路口选择另外一条道继续… 这个过程相当于下面这个二叉树:

用递归实现非常简单:用对应输入集合列表的另一个列表res保存当前这条路上每个元素是取还是舍,然后继续往前走直到列表末尾,最后输出这条路上所有是“取”的元素。实现时为简单起见使用了嵌套函数。

def powerset(nums):
    powerset = []
    res = [0] * len(nums)

    def visit(i):
        if i == len(nums):
            powerset.append([num for j, num in enumerate(nums) if res[j] == 1]) # 输出这条路上所有是“取”的元素
        else:
            res[i] = 0
            visit(i+1) # 先“舍”,继续往前走
            res[i] = 1
            visit(i+1) # 再“取”,继续往前走

    visit(0)

    return powerset

另外一种思路是观察:

[1, 2, 3] -> [[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]

幂集是不是相当于[1, 2, 3]按照[0, 0, 0], [0, 0, 1], … [1, 1, 1]选择出来的?而[0, 0, 0], [0, 0, 1], … [1, 1, 1]既可以看成0-7的二进制表达,也可以看作是3个(0, 1)的笛卡尔积。

实现要点:

[blockquote]

1. 对一个集合按照selector进行选择:

用itertools的compress方法。

2. 将十进制数转换为前面补0的固定长度的二进制数:

用format函数。

3. 求多个集合的笛卡尔积:

用itertools的product方法。

[/blockquote]

借用某个最优解法如下:

from itertools import product, compress

def powerset(nums):
    return [list(compress(nums, pattern)) for pattern in product((0, 1), repeat=len(nums))]

其实,如果没有深度优先的要求,求一个集合的幂集是非常简单的,用itertools模块的combinations方法就可以轻松做到:

from itertools import combinations

def power(s):
    return [list(c) for i in range(len(s)+1) for c in combinations(s, i)]

解决一个问题的方法多多,最重要是善于观察和总结,对吗?

res

相关推荐