如何找到具有n个元素的集合的所有子集?


77

我正在用Python写一个程序,我意识到要解决的一个问题要求我给定一个S包含n元素(| S | = n)的集合,以便对某个特定顺序的所有可能子集m(例如,使用m元素数)。要使用答案产生部分解决方案,然后以下一个阶数m = m + 1再次尝试,直到m = n。

我正在写以下形式的解决方案:

def findsubsets(S, m):
    subsets = set([])
    ...
    return subsets

但是了解Python之后,我希望已经有了解决方案。

做到这一点的最佳方法是什么?


scipy.misc.comb(S, m)给出您将获得的子集数量。由于S的m个子集的数量很快变得非常大,因此最终应该在执行代码之前进行检查。
马丁·托马

从字面上看也有同样的问题,着手自己编写代码,然后意识到必须存在一个Python库!
斯里尼'16

Answers:


129

如果您拥有Python 2.6或更高版本,itertools.combinations是您的朋友。否则,请检查链接以获取等效功能的实现。

import itertools
def findsubsets(S,m):
    return set(itertools.combinations(S, m))

S:您要为其找到子集的集合
m:子集中的元素数


4
我不会返回集合,而只是返回迭代器(或仅使用groups()而不定义findsubsets()...)

@hop OP特别要求设置。省略设定的转换允许以不同的顺序重复,例如:(1,2,3),(2,3,1),(3,1,2)...
James Bradbury

@JamesBradbury:对不起,我不明白你的意思。您是否将此与排列混淆?

62

使用规范函数从itertools配方页面获取功率集

from itertools import chain, combinations

def powerset(iterable):
    """
    powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)
    """
    xs = list(iterable)
    # note we return an iterator rather than a list
    return chain.from_iterable(combinations(xs,n) for n in range(len(xs)+1))

像这样使用:

>>> list(powerset("abc"))
[(), ('a',), ('b',), ('c',), ('a', 'b'), ('a', 'c'), ('b', 'c'), ('a', 'b', 'c')]

>>> list(powerset(set([1,2,3])))
[(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)]

如果需要,可以映射到集合,以便可以使用并集,交集等:

>>> map(set, powerset(set([1,2,3])))
[set([]), set([1]), set([2]), set([3]), set([1, 2]), set([1, 3]), set([2, 3]), set([1, 2, 3])]

>>> reduce(lambda x,y: x.union(y), map(set, powerset(set([1,2,3]))))
set([1, 2, 3])

24

这是一个函数,可为您提供整数[0..n]的所有子集,而不仅仅是给定长度的子集:

from itertools import combinations, chain

def allsubsets(n):
    return list(chain(*[combinations(range(n), ni) for ni in range(n+1)]))

所以例如

>>> allsubsets(3)
[(), (0,), (1,), (2,), (0, 1), (0, 2), (1, 2), (0, 1, 2)]

4
有用的公式,但使用chain.from_iterable而不是扩展可能很长的集合。将组合迭代到列表([ ... ]),扩展星号,链接到迭代器(chain),然后再次变成列表的意义何在?PS。更好的方法是在itertools文档中,在此处提供另一个(较晚的答案)。
亚历克西斯

命名为lambda是不好的做法。使用一个def代替。
wjandrea

@wjandrea它是单线的。
科瓦尔斯基

@Kowalski是什么意思?
wjandrea

@wjandrea我的意思是您编辑之前的代码。
科瓦尔斯基

5

这是一种简单易懂的算法。

import copy

nums = [2,3,4,5]
subsets = [[]]

for n in nums:
    prev = copy.deepcopy(subsets)
    [k.append(n) for k in subsets]
    subsets.extend(prev)

print(subsets) 
print(len(subsets))

# [[2, 3, 4, 5], [3, 4, 5], [2, 4, 5], [4, 5], [2, 3, 5], [3, 5], [2, 5], [5], 
# [2, 3, 4], [3, 4], [2, 4], [4], [2, 3], [3], [2], []]

# 16 (2^len(nums))


1
应该k.append(n)改为k.extend(n)
统一

4

这是一些伪代码-您可以通过在进行递归调用之前检查每个调用的值,然后在递归调用之前检查是否存在调用值,从而削减相同的递归调用。

以下算法将具有除空集以外的所有子集。

list * subsets(string s, list * v) {

    if(s.length() == 1) {
        list.add(s);    
        return v;
    }
    else
    {
        list * temp = subsets(s[1 to length-1], v);
        int length = temp->size();

        for(int i=0;i<length;i++) {
            temp.add(s[0]+temp[i]);
        }

        list.add(s[0]);
        return temp;
    }
}

因此,例如,如果s =“ 123”,则输出为:

1
2
3
12
13
23
123

3

不使用itertools

在Python 3中,您可以用于yield from向buit-inset类添加子集生成器方法:

class SetWithSubset(set):
    def subsets(self):
        s1 = []
        s2 = list(self)

        def recfunc(i=0):            
            if i == len(s2):
                yield frozenset(s1)
            else:                
                yield from recfunc(i + 1)
                s1.append(s2[ i ])
                yield from recfunc(i + 1)
                s1.pop()

        yield from recfunc()

例如下面的代码片段按预期工作:

x = SetWithSubset({1,2,3,5,6})
{2,3} in x.subsets()            # True
set() in x.subsets()            # True
x in x.subsets()                # True
x|{7} in x.subsets()            # False
set([5,3]) in x.subsets()       # True - better alternative: set([5,3]) < x
len(x.subsets())                # 32

的大量使用yield from并带有算法解释
Ori

0

$ python -c "import itertools; a=[2,3,5,7,11]; print sum([list(itertools.combinations(a, i)) for i in range(len(a)+1)], [])" [(), (2,), (3,), (5,), (7,), (11,), (2, 3), (2, 5), (2, 7), (2, 11), (3, 5), (3, 7), (3, 11), (5, 7), (5, 11), (7, 11), (2, 3, 5), (2, 3, 7), (2, 3, 11), (2, 5, 7), (2, 5, 11), (2, 7, 11), (3, 5, 7), (3, 5, 11), (3, 7, 11), (5, 7, 11), (2, 3, 5, 7), (2, 3, 5, 11), (2, 3, 7, 11), (2, 5, 7, 11), (3, 5, 7, 11), (2, 3, 5, 7, 11)]


0
>>>Set = ["A", "B","C","D"]
>>>n = 2
>>>Subsets=[[i for i,s in zip(Set, status) if int(s)  ] for status in [(format(bit,'b').zfill(len(Set))) for bit in range(2**len(Set))] if sum(map(int,status)) == n]
>>>Subsets
[['C', 'D'], ['B', 'D'], ['B', 'C'], ['A', 'D'], ['A', 'C'], ['A', 'B']]

2
解释为什么会那样做。
jwenting

只需尝试一下,您就会发现它是否会按要求做
Mohamed Moawia,

0

使用递归的另一种解决方案:

def subsets(nums: List[int]) -> List[List[int]]:
    n = len(nums)
    output = [[]]
    
    for num in nums:
        output += [curr + [num] for curr in output]
    
    return output

从输出列表中的空子集开始。在每一步中,我们都会考虑一个新的整数,并根据现有整数生成新的子集。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.