python求幂集_求幂集的算法 - Sethylar的个人页面 - OSCHINA - 中文开源技术交流社区...

本文介绍了三种Python求幂集的算法:1)利用二进制数与幂集的对应关系;2)递归和非递归算法;3)通过求不同大小的子集组合。通过这些方法,可以列出一个集合的所有子集,即幂集。
摘要由CSDN通过智能技术生成

昨天看到有人在TL里说一个面试题 : 不用递归法 求一个固定集合内的所有子集。经 @miloyip 指出,这是个power set 的问题,在中文中称为幂集。这个中学数学里就学过了,不过如何用计算机求幂集还真没考虑过,就索性找了找求幂集的算法。根据wiki上的介绍和我自己的理解( http://en.wikipedia.org/wiki/Power_set),给定一个包含n个元素的集合s,我用python实现了三种计算幂集的算法。如果没有额外说明,下文中的 union表示取两个集合的合集。

1. 利用二进制数与幂集之间的对应关系

在[0,2^(n-1)]的整数区间上任取一个值x,x的二进制表示可以用来表示s的一个子集:对于x的第i位,如果为1,则此子集包含s的第i个元素,否则不包含。因此,只要遍历 [0,2^(n-1)]的整数区间,就能列举出s的所有子集,这些子集的集合就是s的幂集。这个方法陈硕老大也指出来了。代码如下:

def power_set(s):

n = len(s)

test_marks = [ 1<

for k in range(0, 2**n):

l = []

for idx, item in enumerate(test_marks):

if k&item:

l.append(s[idx])

yield set(l)

#A simple test

def __test__():

s = [1,2,3,4]

for e in power_set(s):

print e

__test__()

2. 递归计算和一个改进的非递归算法

wiki上给出了一个递归的算法,大致思想是先取出s中的一个元素e,然后求s中其他元素组成的集合的幂集,再把e与求出的幂集中的每个结合进行组合求合集,因此能列举出所有可能的子集。这个算法在列举过程中某些子集可能会重复出现,因此我没有用yield,而是直接返回了一个list。代码如下:

#simply add a set s to list l, with duplicate check

def add_set_to_list(l, s):

if s not in l:

l.append(s)

#F(e, T) = {x union {e} | x belongs to T}

def F(e, T):

l = []

add_set_to_list(l, e)

for x in T:

y = e | x

add_set_to_list(l, y)

return l

def power_set(s):

l = []

if not s: #empty set

l.append(set())

return l

for e in s:

es = set((e,))

t = s - es

s2 = power_set(t)

for i in s2:

add_set_to_list(l, i)

for i in F(es, s2):

add_set_to_list(l, i)

return l

上面这个算法有两个缺点:一是计算过程中会重复生成某些集合,因此 有重复计算,二是它是递归的,效率上可能不高。经过与 @GiantIron 讨论,得到了一种非递归算法,思想跟上面的递归算法类似,但又有区别,而代码要简单得多。非递归算法的数学描述如下:

给定一个集合s,它的幂集表示为P(s), 则对于一个元素e, P( s union {e} } = P(s) union F(e, P(s))。直观地说,已知集合s和它的幂集P(s),如果往s中加入一个新元素e,则形成的新的集合的幂集是 P(s) 和 F(e, P(s)) 的合集。其中F()函数在递归算法的实现中已经给出。上述描述其实跟递归算法中类似,但是我们可以利用这个关系来增量地计算,而不用递归。

代码如下,l用来保存增量计算出来的幂集,由于后续计算依赖于之前计算的结果,这里也没有用yield,直接返回一个list。因为这个计算过程中不会出现重复的子集,所以直接用list.extend()代替了取合集(union)的操作。同样,在递归算法中的F()函数的实现中有检查是否是重复元素的操作,而在这里其实是不必要的:

def power_set(s):

l = [set(),]

for e in s:

l.extend(F(set((e,)), l))

return l

3.  s的幂集 = { subsets(s, k) |  k=0, 1, 2, ..., n}

其中subsets(s,k)表示s所有模为k的子集,即 包含k个元素。这个关系是显而易见的。因此只要求出subsets(s,k),幂集也就很容易了。求subset(s,k)的代码我没有自己写,在这里 有一个实现,也是递归的,它利用了幂集与二项式系数之间的关系以及帕斯卡法则 (其实跟杨辉三角所描述的关系是一样的),显得很优美。代码如下,其中k_subsets(s,k)是别人实现的subsets(s,k)的函数。

def power_set(s):

for k in range(0, len(s)+1):

for k_set in k_subsets(s,k):

yield k_set

除此了上述方法外,陈硕老大也介绍了用stl的next_permutaion()来生成组合数(链接 ),进而求幂集。我觉得这个方法跟上述第三种方法有相似的地方,都是利用了组合与幂集之间的关系,不过生成组合的方法不一样。我相信还有更多的方法可以解决这个问题。

解决同一个问题可以有很多种方法,而看似简单的一个问题,真正要正确地解出来也不是那么容易。由于python本身提供了对set的支持,实现幂集也轻松了很多。不过set对象本身不能作为set的元素,这一点有待推敲,我对python也不是太熟悉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值