生成生成集的算法

Posted

技术标签:

【中文标题】生成生成集的算法【英文标题】:Algorithm to generate spanning set 【发布时间】:2010-10-02 09:47:31 【问题描述】:

给定这个输入:[1,2,3,4]

我想生成一组跨越集:

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

每个集合都包含原始集合的所有元素,排列后出现在唯一的子集中。产生这些集合的算法是什么?我已经尝试使用选择、排列、组合、幂集等 Python 生成器函数,但无法获得正确的组合。

2009 年 1 月 20 日

这不是作业问题。这是我正在为 www.projecteuler.net 问题 #118 研究的改进答案。我已经有了一个缓慢的解决方案,但想出了一个更好的方法——除了我无法弄清楚如何进行跨越集。

我会在就职派对回来后发布我的代码。

2009 年 1 月 21 日

这是我最终使用的算法:

def spanningsets(items):
    if len(items) == 1:
        yield [items]
    else:
        left_set, last = items[:-1], [items[-1]]
        for cc in spanningsets(left_set):
            yield cc + [last]
            for i,elem in enumerate(cc):
                yield cc[:i] + [elem + last] + cc[i+1:]

@Yuval F:我知道如何做一个powerset。这是一个简单的实现:

def powerset(s) :
    length = len(s)
    for i in xrange(0, 2**length) :
        yield [c for j, c in enumerate(s) if (1 << j) & i]
    return

【问题讨论】:

这对 haskell 和 ruby​​ 来说也是个好问题 【参考方案1】:

这个呢?我还没有测试过,但我稍后会尝试......

我认为这种技术称为动态编程:

    取第一个元素[1] 你能用它创造什么?仅限[1]

    拿第二个[2] 现在你有两种可能:[1,2][1] [2]

    拿第三个[3] 使用第 2 号 [1,2] 中的第一个可以创建 [1,2,3][1,2] [3] 使用第二个数字 2 [1] [2] 可以创建 [1,3] [2][1] [2,3][1] [2] [3]

我希望我试图展示的内容足够清楚。 (如果没有,请发表评论!)

【讨论】:

+1 为清楚的例子。每个步骤的一般规则是“将元素放入现有子集之一,或放入新子集中”。 很清楚,但只是为了记录,它不是动态编程。 DP 是指您采用慢速(例如指数时间)递归算法并记录(“memoise”)部分解决方案以便它们可以重复使用,从而产生更快的(例如 O(n^2))算法。 如果您为问题的最小子集创建解决方案,那么动态规划不是动态规划吗? gs:哎呀,我说得太早了。是的,如果您重用通过解决较小的子问题生成的子集,而不是重新生成它们,那么它就是 DP。 我很高兴知道我没有完全误解 DP :)【参考方案2】:

这应该可以工作,虽然我还没有足够的测试。

def spanningsets(items):
    if not items: return
    if len(items) == 1:
        yield [[items[-1]]]
    else:
        for cc in spanningsets(items[:-1]):
            yield cc + [[items[-1]]]
            for i in range(len(cc)):
                yield cc[:i] + [cc[i] + [items[-1]]] + cc[i+1:]

for sset in spanningsets([1, 2, 3, 4]):
    print ' '.join(map(str, sset))

输出:

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

【讨论】:

第 4 行可能是:yield [items] 这是一个非常奇怪的生成器函数。我从来没有见过一个在外循环中有递归的,也没有见过在 else-stmt 中有两个产量的。我肯定要考虑清楚这一点。感谢您提供出色的解决方案! @recursive:最初是这样。我已将其更改为 yield [[items[-1]]] 以强调与循环内的收益率相似。 顺便说一句,我认为最好只传递初始列表中的位置,而不是传递列表的整个部分spanningsets(items[:-1])。这将使我们节省一些内存。并且需要创建一些辅助函数,例如this way【参考方案3】:

这是关于问题的The SUNY algorithm repository 页面。也许您可以将其中一个代码引用翻译成 python。

编辑:这是一个类似的问题。 Here 是关于生成分区的 SUNY 存储库页面,我认为这是正确的问题。

【讨论】:

+1:引用已知正确和完整的算法。 这是相关,但不是相同的问题。电源组不能解决问题。【参考方案4】:

结果集和空集 看起来像 powerset(或幂集)的结果,但不是一回事。

我发布了一篇关于类似问题的帖子,该问题有一些实现(尽管在 C# 中)并且在某些情况下更注重速度而不是清晰度。第一个例子应该很容易翻译。也许无论如何它都会给出一些想法。

他们的工作原理是,对组合进行计数类似于以二进制计数(想象从 0 到 16 计数)。您没有说明顺序是否重要,或者只是生成所有组合,因此之后可能需要快速整理一下。

在这里有一个look(忽略奇怪的标题,讨论转向另一个方向)

【讨论】:

已编辑。只是出于兴趣,Spanning set 和 powerset 有什么区别?该示例的结果将是相同的,不是吗?我知道这并不意味着 ut 适用于所有示例。 幂集是所有子集的集合。生成集的每个成员都是一组集合,其并集是原始集。 例如1, 2, 3 的幂集是 , 1, 2, 1, 2, 3, 1, 3, 2, 3, 1, 2, 3。幂集中总是有 2^n 个集合,因为 n 个基本元素中的每一个都可以在集合中存在或不存在,就像 n 位字中的每个位可以打开或关闭一样。【参考方案5】:

我认为以下方法是为欧拉问题生成它们的最佳方法,因为您可以将返回值替换为素数跨越子集的数量,并且进行乘法将是微不足道的(尤其是记忆化):

GenerateSubsets(list)
    partitions =  x | x is subset of list and x contains the lowest element of list 
    foreach (parition in partitions)
        if set == list
            yield  partition 
        else
            yield  partition  x GenerateSubsets(list - part)

关键部分是确保递归端总是有最左边的元素,这样你就不会得到重复。

我有一些乱七八糟的 C# 代码:

    IEnumerable<IEnumerable<List<int>>> GenerateSubsets(List<int> list)
    
        int p = (1 << (list.Count)) - 2;
        List<int> lhs = new List<int>();
        List<int> rhs = new List<int>();
        while (p >= 0)
        
            for (int i = 0; i < list.Count; i++)
                if ((p & (1 << i)) == 0)
                    lhs.Add(list[i]);
                else
                    rhs.Add(list[i]);

            if (rhs.Count > 0)
                foreach (var rhsSubset in GenerateSubsets(rhs))
                    yield return Combine(lhs, rhsSubset);
            else
                yield return Combine(lhs, null);

            lhs.Clear();
            rhs.Clear();
            p -= 2;
        
    

    IEnumerable<List<int>> Combine(List<int> list, IEnumerable<List<int>> rest)
    
        yield return list;
        if (rest != null)
            foreach (List<int> x in rest)
                yield return x;
    

【讨论】:

以上是关于生成生成集的算法的主要内容,如果未能解决你的问题,请参考以下文章

在点集的凸包内生成系列三角形

生成可重集的排列

如何在python中为apriori算法生成k-itemset

(最小生成树)Kruskal算法

生成对抗网络

如何生成多重集的所有排列?