【问题标题】:Find all sets of strongly-connected vertices查找所有强连接顶点集
【发布时间】:2011-01-05 09:20:16
【问题描述】:

我有一个有向图的边链表,我试图找到所有强连接组件集。任何人都可以向我指出一个具有良好最坏情况时间的算法(非常感谢示例伪代码或 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 的方法吗?

【问题讨论】:

  • 我的答案也扩展到记录边缘

标签: c algorithm graph


【解决方案1】:

维基百科上的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)

根据进一步的规范进行编辑。 您是否看到算法将顶点推入堆栈然后再次将它们弹出?我认为这可能意味着您可以知道每个连续的堆栈元素都通过边缘连接到之前的堆栈元素。我已经修改了上面的伪代码以反映这一点,(但还没有尝试过)。

【讨论】:

  • 我不相信顶点堆栈的排序正确,我在发布问题之前尝试过。我认为堆栈会按顶点的“深度”排序。我想知道该算法是否可以转换为 BFS(如果这样做有什么好处)。
  • 如果节点是强连接的,那么起点无关紧要,它们是一个循环。或者我错过了什么。您需要什么订单?
  • 尼克,如果你看一下我发布的链接中的图片。您将看到图形本身是强连接的。如果图是强连接的,我不相信 Tarjan 将用于查找 SCC。
【解决方案2】:

strongly connected components 的维基百科页面指向三种算法,所有这些算法都在维基百科中进行了足够详细的解释,可以直接翻译成源代码。如果该信息不充分,您可能应该指出究竟缺少什么。

【讨论】:

  • 谢谢,从 wikipedia 的全文中不清楚是否有任何算法会产生所有 SCC 的详尽列表。所以在实施之前,我想我应该问一下。
  • 在 tarjan en.wikipedia.org/wiki/… 的维基百科页面中,它是递归的,其中显示“打印 SCC:”,您应该将顶点添加到 SCC 的结果列表中。
  • 谢谢尼克,将顶点添加到包含我的结果的链表中是相当简单的。但是,我想知道是否有人可以提供修改来存储边而不是顶点?
【解决方案3】:

即使在您进行第二次编辑后,我仍然不确定您的实际目标是什么。您选择的术语会阻碍这一点。大多数人的意思是,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.

然而这是非常浪费的:显然不连通的图是 测试,每个图的连通性都测试了很多次。 我们可以通过搜索可能的边缘集来消除第一个 以更好的顺序,将这些边集排列成格子。每个 边缘集具有由该边缘集减去一个边缘给出的“直接子代”。 一旦我们遇到一个不是 SCC 的图,我们就不需要检查任何 它的子集,因为它们也不能是 SCC。最简单的表达方式 这是作为图形搜索。虽然深度优先搜索通常是 路要走,因为它使用较少的内存,我将首先选择一个广度 搜索,因为稍后我们将使用遍历的顺序。 (一个深度 first search 将队列更改为堆栈)。

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

缓存和队列的结构确实需要做一些工作来确保可以有效地完成所使用的操作,但这相当简单。

【讨论】:

  • 这是最好的答案。感谢您纠正我,我不知道 SCC 始终是最大顶点数。另外,你是对的,我想找到保持强连接的最小边成本。你能解释一下你的“直截了​​当”的算法吗,也许有一些链接或伪代码?
  • @Joe Boo:等等,我以为你想找到所有最小的集合,而不仅仅是最小成本的集合。稍后我会尝试扩展我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多