zoukankan      html  css  js  c++  java
  • 强连通分支——自制算法和经典算法的比较分析

    简介

    有向图G(V,E),圈是一个起始节点与终止节点相同的路径,即 a->….->a。找到所有圈,合并其节点,在图论里就是合并强连通分支。一开始没有找到有效算法,基于深度优先搜索,自制了一个算法。后来找到了一个开源代码【1】,代替了自制算法。本文比较了自制算法和经典算法的区别,总结了经验教训。

    无向图的查圈算法

    深度优先搜索算法是从已知节点出发,图的一种遍历算法。只要一个节点被同源两个路径访问,这两个路径则形成一个圈。因为每个节点只处理一次,所以时间与空间复杂度都是O(N)。其算法如下:

    DFS(a) for undirected graph

    stack.push(a)

    while not stack.empty

    i = stack.pop

    mark i as accessed

    {j} = adjacent(i)

    if j is accessed

              return stack[s,…,r] where stack[s]==stack[t]==j

    stack.push({j})

     

    对于无向图来说,每个 i的邻节点 j ,如果已经 accessed,那么形成一个圈。注意此时只找到了一个圈。简单地改造成多个圈的算法,在算法结束之后,合并节点,再启动。

    有向图的查圈算法DGS

    对于有向图来说,要记录从 a 到 i 的路径,才能判断 i的邻接点j 是否形成一个圈。算法如下:

    DFS(a) for directed graph

    stack.push(<{a}, 0>) //采用 stack 记录路径,集合下标从1开始

    while not stack.empty

    <set, k> = stack.top

    mark set[k++] not in stack // in-stack为路径标记

    if set[k] in stack

    record, merge and pop stack[s,…,r] where stack[s]==stack[t]==set[k]

    mark stack[…] not in stack

    stack.push(<adjacent(stack[s-1].set[k]), 0>) // 合并节点后,重新搜索

    continue

    mark set[k] in stack // set[k]为当前路径上的节点

    if k>|set|

    stack.pop

    else

    stack.push(<adjacent(set[k]), 0>)

     

    这个算法遍历了所有从 a 出发的路径,满足了查找圈的功能。然而要穷举所有路径,这个算法通常会很慢,需要采用一些加速策略。

    查圈算法的加速策略

    通常的策略是对图的简化,各种情况如下:

    1. 0入度、0出度的节点:直接消除。因为所有在圈上的节点的入度和出度数都大于0。这是一个迭代的过程,因为消除一个可能产生另外一个。
    2. 相邻的1入度节点和1出度节点:合并。因为通过其中一个节点必通过另外一个节点。
      • 特殊情况,2度节点,即1入1出的节点。可以形成一个长链,合并成一个节点,加速搜索;也可能与另外节点形成一个圈,直接记录下来,合并。
    3. 无圈子图合并:可以减少搜索长度,代价是要引入额外的数据结构,而且对于成圈的子图集,还要重新验证其中节点的成圈性。

    另一个策略是引入无向图的 accessed标记,每个节点只 access一次,但是会漏掉一些圈。之后再使用完全版本有向图算法。

    还有一个策略是把节点分组,每组节点的生成子图内先行查找,然后在完整的图中查找。

    Tarjan算法

    Tarjan算法是三个经典算法之一【2,3】,基于数组的代码见【1】。

      function strongconnect(v)

        // Set the depth index for v to the smallest unused index

        v.index := index

        v.lowlink := index

        index := index + 1

        S.push(v)

        v.onStack := true

     

        // Consider successors of v

        for each (v, w) in E do

          if (w.index is undefined) then

            // Successor w has not yet been visited; recurse on it

            strongconnect(w)

            v.lowlink  := min(v.lowlink, w.lowlink)

          else if (w.onStack) then

            // Successor w is in stack S and hence in the current SCC

            v.lowlink  := min(v.lowlink, w.lowlink)

          end if

        end for

     

        // If v is a root node, pop the stack and generate an SCC

        if (v.lowlink = v.index) then

          start a new strongly connected component

          repeat

            w := S.pop()

            w.onStack := false

            add w to current strongly connected component

          while (w != v)

          output the current strongly connected component

        end if

      end function

    算法比较

    1. 算法框架:都是 DFS
    2. 堆栈
      • DGS是 DFS 的栈,一个圈放在栈顶
      • Tarjan 是一个特殊的栈,一个连通分支放在栈顶。
    3. 分解策略
      • DGS 没有分解,只是基于节点
      • Tarjan其实基于 DFS 的生成子图,无圈子图的每个节点都有(v.lowlink = v.index),直接就出栈了;有圈子图需要回溯到v.index最小的节点才有(v.lowlink = v.index)。

    总结

    开发大型图算法的注意事项:

    1. 关注经典算法,不要重复发明轮子。掌握利用生成子图的分解技巧。
    2. 关注有向图的统计信息,采取有针对性的加速策略。图的简化可能不是决定性的,但还是有一定效果的。
    3. 对于大型图,很难单步调试。需要自动检测一些约束条件来保证操作的正确性。
      • 对记录下的圈节点,再次验证成圈条件
      • 对节点合并操作
        1. 验证单个节点的前后一致性
        2. 整个图的一致性,例如边的连接关系
        3. 合并操作个数与合并集合基数的对应关系
      • 这方面可参照“最弱前置条件”。

    参考文献

    [1] https://github.com/PetterS/SuiteSparse/blob/master/BTF/Source/btf_strongcomp.c

    [2] https://en.wikipedia.org/wiki/Strongly_connected_component

    [3] https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm

  • 相关阅读:
    HDU 4267 A Simple Problem with Integers
    java实现滑动解锁
    java实现滑动解锁
    java实现滑动解锁
    java实现滑动解锁
    java实现排列序数
    Delphi中文件流的使用方法
    基于Delphi7 WebService 在Apache发布及Apache使用说明
    资源文件的编译
    Delphi下IOC 模式的实现(反转模式,即Callback模式)
  • 原文地址:https://www.cnblogs.com/liuyunfeng/p/6121465.html
Copyright © 2011-2022 走看看