我有一个图表,其中包含未知数量的断开连接的子图。什么是找到它们的好算法(或 Java 库)?
8 回答
我认为您正在寻找的通常称为Flood Fill。是否通过 BFS 或 DFS 遍历图形取决于您。
基本上,您采用未标记(AKA 未着色)节点并为其分配新标签。您将相同的标签分配给与该节点相邻的所有节点,依此类推,分配给从该节点可到达的所有节点。
当无法标记更多可达节点时,您可以通过选择另一个未标记节点重新开始。请注意,这个新节点未标记这一事实意味着它无法从我们之前的节点访问,因此位于另一个断开连接的组件中。
当没有更多未标记的节点时,您必须使用的不同标签的数量就是图形的组件数。每个节点的标签告诉您哪个节点是哪个组件的一部分。
Not a Java implementation but perhaps it will be useful for someone, here is how to do it in Python:
import networkx as nx
g = nx.Graph()
# add nodes/edges to graph
d = list(nx.connected_components(g))
# d contains disconnected subgraphs
# d[0] contains the biggest subgraph
More information here.
这个问题有很多方面没有得到充分解释,所以我将给出一个有点详尽的答案。尽管我倾向于发布文字墙。:/ 另请注意,我假设这是一个家庭作业问题或自学问题,所以我不会直接给出答案。
检测图连通性的两种基本算法是深度优先搜索和广度优先搜索。这些确实是您要查看的两个起点。两者都会让你找到解决方案,但方式不同,如果不考虑问题的一些非常深入的方面,很难争论哪个“更好”。但让我们继续前进。
正如我之前提到的,你遗漏了一些重要的细节,我将在这里讨论一些可能性。
你的图是有向的还是无向的?您是否考虑“强”意义上的连通性(在这种情况下,请参阅 oggy 的回答),还是“弱”意义上的连通性?根据您的答案,您将不得不以一种稍微不同的方式来处理您的算法。请注意,对于无向图,弱连接和强连接是等价的,这很好。但是无论如何,在实施或寻找算法时,您都必须牢记图形的结构。
此外,还有一个问题是“找到子图”(释义)是什么意思。通常图连接是一个决策问题——简单地说“有一个连接的图”或“有两个或多个子图(也就是断开连接)”。拥有一个算法需要最少的工作量,这很好。:) 下一步将是图表的数量,字面意思是图表的数量,而且书本也不错。倒数第二,您可能需要每个子图中的节点列表。最后,您可能希望逐字复制出子图、边和所有(因此返回类型将是一个图列表,我想,其中每个图都是连接的隐含不变量)。这些不同的结果类型都不需要不同的算法,需要不同的方法来处理书本工作。
对于一个非常基本的问题,所有这些似乎都是荒谬的矫枉过正,但我想我只想强调即使是这样一个简单的图形问题所涉及的所有方面。作为一种悬念,请注意这些甚至都没有涉及运行时或内存使用!:) - 阿戈尔
广度优先搜索以查找所有连接节点怎么样?获得连接节点列表后,从所有节点列表中减去该列表。你最终得到一个断开连接的节点列表
我假设您想找到所有(强)连接的组件?为此,您可以使用Tarjan 算法(DFS 的一种变体)
我可能应该找到一个标准算法(维基百科有一些建议),但我自己想出了这个算法,它对我的目的很有效。我的 C# 实现需要几秒钟来处理具有 40,000 个节点和 44,000 条边的图,以找到 160 个子图和 20,000 个未连接的节点。我使用 HashSet 来存储每个子图组,因此测试组成员关系大约为 O(1),整个算法变为 O(E*C),其中 E 是边数,C 是图中的连通分量数. Wikipedia 提到了一个 O(N) 算法,它与节点数量呈线性关系,所以我确信我的算法效率不是最高的,但它对我的应用程序来说已经足够快了。(编辑:我也在掩饰合并组的成本,所以不要过分强调我的复杂性分析。)
逻辑:
For each edge A->B
If A is in a group and B is not, add B to group A
If A is not in a group and B is, add A to group B
If A and B are in different groups, merge the groups
If neither A or B is in a group, create new group containing A and B
伪代码:
graph = {nodes, edges}
groups = {}
foreach edge A->B in graph.edges:
groupA = findgroup(A)
groupB = findgroup(B)
if (groupA && !groupB)
groupA.add(B)
elif (!groupA && groupB)
groupB.add(A)
elif (groupA && groupB)
if (groupA != groupB)
groupA.union(groupB)
groups.delete(groupB)
else
groups.add({A,B})
findgroup(node):
for group in groups:
if group.contains(node):
return group
return null
请注意,这不会捕获任何未参与边缘的节点。对于我的特定目的,这很好。如果你想获得所有的单节点组,你可以做一个最后的传递:
foreach node in graph.nodes:
group = findgroup(node)
if !group:
groups.add({node})