按共享元素对集合列表进行分区

时间:2008-10-07 15:08:34

标签: sql algorithm set

这是问题的主旨:给出一组集合,例如:

[ (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) ] ]

注意stickeyness - 集合(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的答案使用旧的列名称。

4 个答案:

答案 0 :(得分:6)

问题恰恰是超图的连通分量的计算:整数是顶点,而集合是超边界。计算连通组件的一种常用方法是将它们一个接一个地充满:

  • 对于所有i = 1到N,执行:
  • 如果我被某些j<我,然后继续(我的意思是跳到下一个)
  • else flood_from(i,i)

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

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

然后,这些集的标签会为您提供所需的连接组件。


就数据库而言,算法可以表示如下:向数据库添加TAG列,然后通过执行

计算set i的连通组件
  • S =选择set_id == i
  • 的所有行
  • 为S
  • 中的行设置TAG为i
  • S'=选择未设置TAG的所有行以及元素在元素(S)中的位置
  • 虽然S'不为空,但是
  • ----为S'
  • 中的行设置TAG为i
  • ---- S''=选择未设置TAG的所有行以及元素在元素中的位置(S')
  • ---- S = S union S'
  • ---- S'= S''
  • return set_id(S)

提出此算法的另一种(理论)方式是说您正在寻找映射的固定点:

  • 如果A = {A 1 ,...,A n }是一组集合,则定义union(A)= A 1 union ... union A n
  • 如果K = {k 1 ,...,k p }是一组整数,则定义入射(K)=与K相交的集合集

然后,如果S是一个集合,则通过在S上迭代(发生)o(并集)直到达到一个固定点来获得S的连通分量:

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

答案 1 :(得分:1)

您可以将其视为一个图形问题,其中集合(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]]

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

在考虑了这个之后,我想出了这个:

  1. 使用列groups
  2. 创建一个名为(group_id, set_id)的表格
  3. setselement表格进行排序。现在应该很容易找到重复的元素。
  4. 遍历sets表,当你找到一个重复的元素时:
    1. 如果set_id表中存在groups个字段之一,请添加另一个group_id字段。
    2. 如果set_id表中不存在groups,请生成新的组ID,并将set_id添加到groups表中。
  5. 最后,我应该有一个包含所有集合的groups表。

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

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