【问题标题】:Union find implementation using Python使用 Python 联合查找实现
【发布时间】:2013-11-22 20:58:34
【问题描述】:

所以这就是我想要做的:我有一个包含几个等价关系的列表:

l = [[1, 2], [2, 3], [4, 5], [6, 7], [1, 7]]

我想合并共享一个元素的集合。这是一个示例实现:

def union(lis):
  lis = [set(e) for e in lis]
  res = []
  while True:
    for i in range(len(lis)):
      a = lis[i]
      if res == []:
        res.append(a)
      else:
        pointer = 0 
        while pointer < len(res):
          if a & res[pointer] != set([]) :
            res[pointer] = res[pointer].union(a)
            break
          pointer +=1
        if pointer == len(res):
          res.append(a)
     if res == lis:
      break
    lis,res = res,[]
  return res

然后打印出来

[set([1, 2, 3, 6, 7]), set([4, 5])]

这是正确的,但是当等价关系太大时太慢了。我查了关于联合查找算法的描述:http://en.wikipedia.org/wiki/Disjoint-set_data_structure 但我仍然在编写 Python 实现时遇到问题。

【问题讨论】:

  • 我不确定您是否需要自己实现或可以使用现有模块,但在后一种情况下 NetworkX has an implementation of Union-Find 对我来说效果很好。
  • 请参阅this question 了解许多实现和一些时序测试。 [这通常称为“集合合并”。]
  • 看看我的 O(n) 时间复杂度解决方案。所有其他解决方案都给出 O(n^2)。你的清单可以有多大?我已经测试了一大组 100,000 个 2 元组,它在不到一秒的时间内运行(0.5 秒到 0.8 秒)。
  • 当你只做联合时,你不需要联合查找。一个简单的洪水填充效果很好,速度更快。

标签: python list union-find


【解决方案1】:

O(n) 时间运行的解决方案

def indices_dict(lis):
    d = defaultdict(list)
    for i,(a,b) in enumerate(lis):
        d[a].append(i)
        d[b].append(i)
    return d

def disjoint_indices(lis):
    d = indices_dict(lis)
    sets = []
    while len(d):
        que = set(d.popitem()[1])
        ind = set()
        while len(que):
            ind |= que 
            que = set([y for i in que 
                         for x in lis[i] 
                         for y in d.pop(x, [])]) - ind
        sets += [ind]
    return sets

def disjoint_sets(lis):
    return [set([x for i in s for x in lis[i]]) for s in disjoint_indices(lis)]

工作原理:

>>> lis = [(1,2),(2,3),(4,5),(6,7),(1,7)]
>>> indices_dict(lis)
>>> {1: [0, 4], 2: [0, 1], 3: [1], 4: [2], 5: [2], 6: [3], 7: [3, 4]})

indices_dict 给出从等价 # 到 lis 中的索引的映射。例如。 1 映射到lis 中的索引04

>>> disjoint_indices(lis)
>>> [set([0,1,3,4], set([2])]

disjoint_indices 给出了一组不相交的索引列表。每个集合对应于等价中的索引。例如。 lis[0]lis[3] 等价,但lis[2] 不等价。

>>> disjoint_set(lis)
>>> [set([1, 2, 3, 6, 7]), set([4, 5])]

disjoint_set 将不相交的索引转换为其适当的等价物。


时间复杂度

O(n) 的时间复杂度很难看出,但我会尝试解释一下。这里我将使用n = len(lis)

  1. indices_dict 肯定会在 O(n) 时间内运行,因为只有 1 个 for 循环

  2. disjoint_indices 是最难看到的。它肯定会在O(len(d)) 时间内运行,因为当d 为空时外部循环停止并且内部循环在每次迭代中删除d 的元素。现在,len(d) &lt;= 2n 因为d 是从等价# 到索引的映射,并且lis 中最多有2n 不同的等价#。因此,函数运行在O(n)

  3. disjoint_sets 很难看到,因为有 3 个组合的 for 循环。但是,您会注意到,i 最多可以运行 lis 中的所有 n 索引,而 x 运行 2 元组,因此总复杂度为 2n = O(n)

【讨论】:

    【解决方案2】:

    我认为这是一个优雅的解决方案,使用内置的 set 函数:

    #!/usr/bin/python3
    
    def union_find(lis):
        lis = map(set, lis)
        unions = []
        for item in lis:
            temp = []
            for s in unions:
                if not s.isdisjoint(item):
                    item = s.union(item)
                else:
                    temp.append(s)
            temp.append(item)
            unions = temp
        return unions
    
    
    
    if __name__ == '__main__':
        l = [[1, 2], [2, 3], [4, 5], [6, 7], [1, 7]]
        print(union_find(l))
    

    它返回一个集合列表。

    【讨论】:

      【解决方案3】:

      也许是这样的?

      #!/usr/local/cpython-3.3/bin/python
      
      import copy
      import pprint
      import collections
      
      def union(list_):
          dict_ = collections.defaultdict(set)
      
          for sublist in list_:
              dict_[sublist[0]].add(sublist[1])
              dict_[sublist[1]].add(sublist[0])
      
          change_made = True
          while change_made:
              change_made = False
              for key, values in dict_.items():
                  for value in copy.copy(values):
                      for element in dict_[value]:
                          if element not in dict_[key]:
                              dict_[key].add(element)
                              change_made = True
      
          return dict_
      
      list_ = [ [1, 2], [2, 3], [4, 5], [6, 7], [1, 7] ]
      pprint.pprint(union(list_))
      

      【讨论】:

      • 我敢打赌 OP 正在寻找一个功能来做到这一点......你可以考虑将 main 重命名为 union,并摆脱你的 (very) 特定的 shebang 行。
      • 我把它变成了一个函数。关于#! line:很容易更改,它显示了我测试的 Python 版本。
      【解决方案4】:

      这是通过一次完全耗尽一个等价物来实现的。当一个元素找到它的等价物时,它就会从原始集合中删除并且不再被搜索。

      def equiv_sets(lis):
          s = set(lis)
          sets = []
      
          #loop while there are still items in original set
          while len(s):
              s1 = set(s.pop())
              length = 0
              #loop while there are still equivalences to s1
              while( len(s1) != length):
                  length = len(s1)
                  for v in list(s):
                      if v[0] in s1 or v[1] in s1:
                          s1 |= set(v)
                          s  -= set([v])
              sets += [s1]
          return sets
      
      print equiv_sets([(1,2),(2,3),(4,5),(6,7),(1,7)])
      

      输出: [set([1, 2, 3, 6, 7]), set([4, 5])]

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-08-01
        • 2011-05-28
        • 1970-01-01
        • 1970-01-01
        • 2021-06-02
        • 1970-01-01
        • 2021-11-30
        • 1970-01-01
        相关资源
        最近更新 更多