首页 > 编程知识 正文

幂集的运算,幂集运算性质

时间:2023-05-06 13:56:15 阅读:255833 作者:4166

求幂集的算法

昨天看到有人在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的幂集。这个方法fkdxhd老大也指出来了。代码如下:

def power_set(s):    n = len(s)    test_marks = [ 1<<i for i in range(0, n) ]    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 testdef __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 checkdef 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 ldef 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

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

 

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


转载于:https://my.oschina.net/sethylar/blog/323227

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。