寻找非常大的图形的组件

时间:2013-08-21 16:52:49

标签: python algorithm

我有一个非常大的图表,在一个大小约1TB的文本文件中表示,每个边缘如下。

From-node to-node

我想把它拆分成弱连接的组件。如果它更小,我可以将其加载到networkx并运行其组件查找算法。例如 http://networkx.github.io/documentation/latest/reference/generated/networkx.algorithms.components.connected.connected_components.html#networkx.algorithms.components.connected.connected_components

有没有办法在不将整个内容加载到内存中的情况下执行此操作?

3 个答案:

答案 0 :(得分:10)

如果你有足够的节点(例如几亿),那么你可以通过使用存储在内存中的disjoint set forest一次通过文本文件计算连接的组件。

此数据结构仅存储每个节点的排名和父指针,因此如果节点足够少,则应该适合内存。

对于大量节点,您可以尝试相同的想法,但将数据结构存储在磁盘上(并且可能通过在内存中使用缓存来存储经常使用的项目来改进)。

这是一些Python代码,它实现了一个简单的内存版本的不相交集合林:

N=7 # Number of nodes
rank=[0]*N
parent=range(N)

def Find(x):
    """Find representative of connected component"""
    if  parent[x] != x:
        parent[x] = Find(parent[x])
    return parent[x]

def Union(x,y):
    """Merge sets containing elements x and y"""
    x = Find(x)
    y = Find(y)
    if x == y:
        return
    if rank[x]<rank[y]:
        parent[x] = y
    elif rank[x]>rank[y]:
        parent[y] = x
    else:
        parent[y] = x
        rank[x] += 1

with open("disjointset.txt","r") as fd:
    for line in fd:
        fr,to = map(int,line.split())
        Union(fr,to)

for n in range(N):
    print n,'is in component',Find(n)

如果将其应用于名为disjointset.txt的文本文件,其中包含:

1 2
3 4
4 5
0 5

打印

0 is in component 3
1 is in component 1
2 is in component 1
3 is in component 3
4 is in component 3
5 is in component 3
6 is in component 6

您可以通过不使用秩数组来节省内存,但代价是可能会增加计算时间。

答案 1 :(得分:1)

外部存储器图遍历很难获得高效率。我建议不要编写自己的代码,实现细节会在几个小时的运行时间和几个月的运行时间之间产生差异。您应该考虑使用stxxl之类的现有库。有关使用它计算连通组件的论文,请参阅here

答案 2 :(得分:1)

如果节点的数量太大而无法容纳在内存中,您可以分而治之并使用外部内存排序为您完成大部分工作(例如sort Windows和Unix附带的命令可以对比内存大得多的文件进行排序):

  1. 选择一些阈值顶点k。
  2. 阅读原始文件并将其每个边缘写入3个文件中的一个:
    • 如果a的最大编号顶点是&lt; ķ
    • 如果b的最小编号顶点为&gt; = k
    • ,则为c
    • 否则为a(即如果它有一个顶点&lt; k且一个顶点&gt; = k)
  3. 如果x y小到足以在内存中找到(找到连接的组件)(使用例如Peter de Rivaz's algorithm),那么这样做,否则递归来解决它。解决方案应该是一个文件,其行每个都由两个数字x组成,并按x排序。每个y是一个顶点编号,x代表 - 与b在同一个组件中编号最小的顶点。
  4. 同样为c
  5. c的最小编号端点对边缘进行排序。
  6. 浏览a中的每个边缘,重命名&lt; k(记住,必须有一个这样的端点)到其代表,从子问题a的解决方案中找到。这可以通过使用线性时间合并算法与解决方案合并到子问题d来有效地完成。调用生成的文件d
  7. d的最大编号端点对边缘进行排序。 (事实上​​我们已经重命名了最小编号的端点并不会使这不安全,因为重命名永远不会增加顶点的数量。)
  8. 浏览b中的每个边缘,将&gt; = k的端点重命名为其代表,使用如前所述的线性时间合并从解决方案找到子问题e。调用生成的文件e
  9. 解决a。 (与be一样,如果可能的话,直接在内存中执行此操作,否则递归。如果需要递归,则需要找到一种不同的分割边缘的方法,因为所有边缘都在a已经“跨越”k。例如,您可以使用顶点数字的随机排列重新编号顶点,递归以解决结果问题,然后将其重命名。)此步骤是必要的,因为可能存在边缘(1 ,k),另一个边(2,k + 1)和第三个边(2,k),这将意味着组件1,2,k和k + 1中的所有顶点需要组合成一个组件
  10. 浏览子问题e的解决方案中的每一行,如有必要,使用子问题a的解决方案更新此顶点的代表。这可以使用线性时间合并来有效地完成。写出新的代表列表(由于我们是从f的解决方案创建的,因此已经按顶点数排序)到文件b
  11. 同样针对子问题g的解决方案中的每一行,创建文件f
  12. 连接gf以产生最终答案。 (为了提高效率,只需让步骤11将结果直接附加到{{1}})。
  13. 上面使用的所有线性时间合并操作都可以直接从磁盘文件中读取,因为它们只能按递增的顺序访问每个列表中的项目(即不需要慢速随机访问)。