我有一个有向图的边连接列表,我试图找到所有强连接组件集。任何人都可以指向一个具有良好最坏情况时间的算法(样本伪或C代码将非常感激)。
编辑:我试图找到创建强连接组件而不是顶点的所有边集。在下面的图表中,注意到有2组边缘用于创建强连接组件,但是图表上仅两个边缘用于两者(a-> b和b-> c)。该算法应该能够产生集合{a-> b,b-> c,c-> a}和{a-> b,b-> c,c-> b,b-> ; a}。
http://img521.imageshack.us/img521/8025/digraph.jpg
希望这有助于更明确我的目标。
EDIT2:我有一个半工作的实现,但是我注意到,如果我搜索的图形也是强连接的,它不起作用。有人知道在SCC中找到SCC的方法吗?
答案 0 :(得分:3)
维基百科上的Strongly connected component定义推荐了三种算法。我会将Tarjan's作为效率和易于实施的最佳组合。
我在维基百科上采用了伪代码,并进行了修改以保留所有SCC的列表。它如下:
Input: Graph G = (V, E)
List<Set> result = empty
index = 0 // DFS node number counter
S = empty // An empty stack of nodes
for all v in V do
if (v.index is undefined) // Start a DFS at each node
tarjan(v, result) // we haven't visited yet
procedure tarjan(v, result)
v.index = index // Set the depth index for v
v.lowlink = index
index = index + 1
S.push(v) // Push v on the stack
for all (v, v2) in E do // Consider successors of v
if (v2.index is undefined) // Was successor v' visited?
tarjan(v2, result) // Recurse
v.lowlink = min(v.lowlink, v2.lowlink)
else if (v2 is in S) // Was successor v' in stack S?
v.lowlink = min(v.lowlink, v2.index) // v' is in stack but not the dfs tree
if (v.lowlink == v.index) // Is v the root of an SCC?
set interconnected = empty
previous = v
repeat
v2 = S.pop
interconnected.add((previous,v2)) // record this edge
last = previous=v2
until (v2 == v)
result.add(interconnected)
编辑以响应进一步的规范。 你是否看到算法将顶点推入堆栈然后再将它们弹出?我认为这可能意味着您可以知道每个连续的堆栈元素都通过边连接到前一个堆栈元素。我已修改上面的伪代码以反映这一点,(但尚未尝试过)。
答案 1 :(得分:1)
strongly connected components的维基百科页面指向三种算法,所有这些算法都在维基百科中详细解释,以便直接转换为源代码。如果该信息不足,您可能应该指出究竟缺少的信息。
答案 2 :(得分:1)
即使在第二次编辑之后,我仍然不确定你的目标是什么。您选择的术语会妨碍这一点。 SCC,因为大多数人都认为它们是最大的顶点集,它们都可以相互访问,并且可能发生的路径无关紧要。
有人知道在SCC中找到SCC的方法吗?
根据通常的定义,SCC中没有SCC,因为SCC是最大的。
您正在寻找其他东西:您从连接图开始,并希望找到您没有很好地表征的特定边集。我强烈怀疑如果你以明确的方式表征你想要的东西,那么算法就会失败。
我的第一个猜测是,对于给定的连通图,您需要连接每对的所有路径的所有可能组合的边集。如果是这种情况,那么直接的方法是:对于每一对,找到所有路径,然后找到所有组合并消除重复。但是,这会产生比您在示例中列出的更多的集合,因为使用所有边缘肯定是可能的。
相反,您似乎希望所有最小边集仍然让原始顶点保持为SCC。在这种情况下,原始边集的power set的每个元素代表一个可能的候选者,并且您希望图表仍然连接的所有候选者,但没有仍然连接的正确子集。
这给出了一个简单的算法,即走遍这个集合点并检查这个条件。检查图形是否连接是直接的,删除或添加边缘也是如此。但是,类似的图表具有大部分相同的结构,因此您需要重用以前的问题。
一些可能有用的参考资料:
特别是在将图表的全局信息连接到有关边和顶点的局部信息方面。
你的起始图G有一组顶点V和边E,并且是强连接的。 目标是输出所有“最小”边缘集E',使得从E中移除任何边缘将图形分成多个SCC。我们可以通过搜索所有可能的边集来完成此任务。
直截了当地说:
for E' in powerset(E):
if (V, E') strongly connected:
for e in E':
if (V, E' - e) strongly connected:
try next E' # Not minimal
add G' = (V, E') to list of minimal subsets.
然而,这非常浪费:显然没有连接的图表 经过测试,每个图表的连通性经过多次测试。 我们可以通过搜索可能的边集来消除第一个 以更好的顺序,将这些边集排列在格子中。每 edgeset具有由该边集减去一条边的“直接子节点”。 一旦我们遇到不是SCC的图表,我们就不需要检查任何一个 它的子集,因为它们也不能是SCC。最简单的表达方式 这是一个图搜索。虽然深度优先搜索通常是 要走的路,因为它使用较少的内存,我将首先选择广度 搜索,因为稍后我们将使用遍历的顺序。 (深度 第一次搜索将队列更改为堆栈。)
push starting node on queue
while queue not empty:
pop node from queue
for c in suitable children, not on queue:
push c on queue
deal with node
“不在队列”部分至关重要,否则可以访问每个节点 多次。这将我们的算法变成:
push E on queue
while queue not empty:
pop E' from queue
if (V, E') strongly connected:
minimal = true
for e in E':
if (V, E' - e) strongly connected:
push E' - e on queue if not on queue
minimal = false
if minimal:
add G' = (V, E') to list of minimal subsets.
现在它会跳过所有不可能强连接的边集, 因为超集也不是。缺点是它可以 可能会占用大量空间。但是,它仍然会反复检查 连接。但是,我们可以缓存这些信息。
check_or_add_cached_connected(E)
push E on queue
while queue not empty:
pop E' from queue
connected = cached (E')
if connected:
minimal = true
for e in E':
child_connected = check_or_add_cache(E' - e)
if child_connected:
push E' - e on queue if not on queue
minimal = false
if minimal:
add G' = (V, E') to list of minimal subsets.
remove E' from cache
现在我们已经开始缓存,我们可以缓存更多内容。如果 删除边会破坏图形,它也会破坏任何子图形。 这意味着我们可以在图表中跟踪“所需边缘” 将它们传播到任何子图。我们可以检查这些要求 边缘以避免检查子图。
check_or_add_cached_connected(E)
push E on queue
while queue not empty:
pop E' from queue
(connected, required_edges) = cached (E')
if connected:
minimal = true
for e in E' and not in required_edges:
child_connected = check_or_add_cached_connected(E' - e)
if child_connected:
push E' - e on queue if not on queue
minimal = false
else:
add e to required_edges
if minimal:
add G' = (V, E') to list of minimal subsets.
else:
for e in E' and not in required_edges:
merge_cache(E' - e, required_edges)
remove E' from cache
缓存和队列的结构确实需要一些工作来确保可以有效地完成所使用的操作,但这非常简单。