Python:在显示为边列表的图中查找连接组件

问题描述

我有一个表单中的边列表

start | end
 a       c
 b       d
 e       b

我有数千万条边(大约 3000 万条),我无法将整个图形读入内存 - 至少没有使用像 networkx 这样的库,它有点占用内存。

我的目标是在由该列表表示的图中找到包含少于 x 个节点的所有连通组件。

例如,我想获得少于 x=30 节点的所有连接组件。但我不想通过构建整个图然后搜索连接的组件来做到这一点(例如,调用这个 networkx 命令:nx.connected_component_subgraphs(nxg))。

有没有一种方法可以只使用边列表文件搜索连接的组件,而无需构建整个图?

附加信息:节点名称是长度为 10-20 个 asci 值的字符串。

解决方法

您首先需要通过为节点分配较短的标识符来减少数据占用空间。您可以将这些较短标识符和原始名称之间的映射写入另一个文件,以便在运行算法后将任何解决方案转换为这些名称。

假设您的节点标识符足够短,您可以将所有内容加载到内存中,以节点标识符为键的字典中。

然后使用 Union-Find 结构和算法来识别连通分量。

最后根据允许的最大大小过滤它们。

有一些库提供了 Union-Find 实现,可以提供更好的性能。下面是 Union-Find 的一个简单实现:

class Node:
    def __init__(self,key):
        self.key = key
        self.parent = self
        self.size = 1

class UnionFind(dict):
    def find(self,key):
        node = self.get(key,None)
        if node is None:
            node = self[key] = Node(key)
        else:
            while node.parent != node: 
                # walk up & perform path compression
                node.parent,node = node.parent.parent,node.parent
        return node

    def union(self,key_a,key_b):
        node_a = self.find(key_a)
        node_b = self.find(key_b)
        if node_a != node_b:  # disjoint? -> join!
            if node_a.size < node_b.size:
                node_a.parent = node_b
                node_b.size += node_a.size
            else:
                node_b.parent = node_a
                node_a.size += node_b.size

然后,以下函数将从迭代器加载该结构,并返回大小符合要求的组件:

from collections import defaultdict

def find_components(line_iterator,max_size):
    forest = UnionFind()

    for line in line_iterator:
        forest.union(*line.split())

    result = defaultdict(list)
    for key in forest.keys():
        root = forest.find(key)
        if root.size <= max_size:
            result[root.key].append(key)

    return list(result.values())

这是下图的演示:

enter image description here

data = """x d
c j
i e
f x
n z
a u
g r
w x
p l
u o
m g
k s
t q
y l
h m
n b
k v
e u
i o
r m
n c
x q
f q
j l
s v"""

results = find_components(data.splitlines(),5)
print(results)

这个演示的输出是:

[['i','e','a','u','o'],['g','r','m','h'],['k','s','v']]