【问题标题】:Partition a list of sets by shared elements按共享元素对集合列表进行分区
【发布时间】:2008-10-07 15:08:33
【问题描述】:

这是问题的要点:给定一个集合列表,例如:

[ (1,2,3), (5,2,6), (7,8,9), (6,12,13), (21,8,34), (19,20) ]

返回集合的组列表,使得具有共享元素的集合在同一个组中。

[ [ (1,2,3), (5,2,6), (6,12,13) ], [ (7,8,9), (21,8,34) ], [ (19,20) ] ]

注意粘性 - 集合 (6,12,13)​​ 没有与 (1,2,3) 共享的元素,但由于 (5,2,6),它们被放在同一个组中。

为了使事情复杂化,我应该提一下,我实际上并没有这些整洁的集合,而是一个包含数百万行的 DB 表,如下所示:

element | set_id
----------------
1       | 1
2       | 1
3       | 1
5       | 2
2       | 2
6       | 2

等等。所以我很想用 SQL 来做这件事,但我会对解决方案的总体方向感到满意。

编辑:将表列名称更改为 (element, set_id) 而不是 (key, group_id),以使术语更加一致。请注意,Kev 的答案使用旧的列名。

【问题讨论】:

    标签: sql algorithm set


    【解决方案1】:

    问题正是超图连通分量的计算:整数是顶点,集合是超边。计算连接组件的一种常用方法是一个接一个地泛洪它们:

    • 对于所有 i = 1 到 N,请执行以下操作:
    • 如果 i 已被某个 j
    • else flood_from(i,i)

    其中 flood_from(i,j) 将被定义为

    • 对于每个包含 i 的集合 S,如果它还没有被 j 标记,则:
    • 用j标记S,对于S的每个元素k,如果k还没有用j标记,那么用j标记它,并调用flood_from(k,j)

    集合的标签然后为您提供您正在寻找的连接组件。


    在数据库方面,算法可以表示为:在数据库中添加一个 TAG 列,然后通过做来计算集合 i 的连通分量

    • S = 选择 set_id == i 的所有行
    • 将 S 中的行的 TAG 设置为 i
    • S' = 选择未设置 TAG 且元素位于元素 (S) 中的所有行
    • 当 S' 不为空时,做
    • ----为S'中的行设置TAG为i
    • ---- S'' = 选择未设置 TAG 且元素位于元素 (S') 中的所有行
    • ---- S = S union S'
    • ---- S' = S''
    • 返回 set_id(S)

    表示此算法的另一种(理论)方式是说您正在寻找映射的不动点:

    • 如果 A = {A1, ..., An} 是一组集合,则定义 union(A) = A1 sub> union ... union An
    • 如果 K = {k1, ..., kp} 是一个整数集合,则定义发病率 (K) = 与 K 相交的集合

    那么如果S是一个集合,则通过在S上迭代(incidences)o(union)直到到达一个不动点,得到S的连通分量:

    1. K = S
    2. K' = 发生率(联合(K))。
    3. 如果 K == K',则返回 K,否则 K = K' 并转到 2。

    【讨论】:

    • 感谢您的努力!你能看看我的回答,告诉我是不是错了,和你的解决方案基本一样,还是只是不同的解决方案?
    • 在我看来,您需要一些合并步骤才能完成您的解决方案:您可以为应该在同一组中的集合启动不同的 goup_ids,因为您还没有发现。如果您有一个副本并且两组都在不同的组中,请合并这两个组。
    【解决方案2】:

    您可以将其视为一个图问题,其中集合 (1,2,3) 通过 2 连接到集合 (5,2,6)。然后使用标准算法来细化连接的子-图表。

    这是一个快速的python实现:

    nodes = [ [1,2,3], [2,4,5], [6,7,8], [10,11,12], [7,10,13], [12], [] ]
    links = [ set() for x in nodes ]
    
    #first find the links
    for n in range(len(nodes)):
        for item in nodes[n]:
            for m in range(n+1, len(nodes)):
                if (item in nodes[m]):
                    links[n].add(m)
                    links[m].add(n)
    
    sets = []
    nodes_not_in_a_set = range(len(nodes))
    
    while len(nodes_not_in_a_set) > 0:
        nodes_to_explore = [nodes_not_in_a_set.pop()]
        current_set = set()
        while len(nodes_to_explore) > 0:
            current_node = nodes_to_explore.pop()
            current_set.add(current_node)
            if current_node in nodes_not_in_a_set:
                nodes_not_in_a_set.remove(current_node)
            for l in links[current_node]:
                if l not in current_set and l not in nodes_to_explore:
                    nodes_to_explore.append(l)
        if len(current_set) > 0:
            sets.append(current_set)
    
    for s in sets:
        print [nodes[n] for n in s]
    

    输出:

    [[]]
    [[6, 7, 8], [10, 11, 12], [7, 10, 13], [12]]
    [[1, 2, 3], [2, 4, 5]]
    

    【讨论】:

    • 您能详细说明一下吗?
    • 如果没有 lft,rgt 策略,这是很难做到的(除非你想要大量循环来遍历树)
    【解决方案3】:

    这可能效率很低,但至少应该可以:从一个键开始,选择包含该键的所有组,选择这些组的所有键,选择包含这些键的所有组,等等,然后只要一个步骤没有添加新的键或组,您就会拥有一个子图的所有组的列表。排除这些并从头开始重复,直到没有数据为止。

    我认为,就 SQL 而言,这需要一个存储过程。 WITH RECURSIVE 可能会以某种方式帮助您,但我还没有任何经验,而且我不确定它是否可以在您的数据库后端使用。

    【讨论】:

      【解决方案4】:

      又想了想,想到了这个:

      1. 创建一个名为groups 的表,其列(group_id, set_id)
      2. elementsets 表进行排序。现在应该很容易找到重复的元素。
      3. 遍历集合表,当您发现重复元素时,执行以下操作:
        1. 如果set_id 字段之一存在于groups 表中,则添加具有相同group_id 的另一个字段。
        2. 如果groups 表中不存在set_id,则生成新的组ID,并将set_ids 添加到groups 表中。

      最后我应该有一个包含所有集合的groups 表。

      这不是纯 SQL,但看起来像 O(nlogn),所以我想我可以忍受。

      Matt's answer 似乎更正确,但我不确定如何在我的情况下实现它。

      【讨论】:

        猜你喜欢
        • 2017-10-04
        • 2011-06-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-27
        • 2016-07-16
        相关资源
        最近更新 更多