zoukankan      html  css  js  c++  java
  • Tarjan强连通分量(scc)

    概念解释

    1. 节点强连通(v_i)(v_j)((v_i ≠ v_j))强连通是指从(vi)(vj)和从(vj)(vi)都存在路径,即两节点互相可达

    2. 强连通图:在有向图(G)中,对于每一对(v_i、v_j)(v_i ≠ v_j),从(v)i(到)v_j(和从)v_j(到)v_i$都存在路径,则称G是强连通图。即任意两个节点强连通的有向图

    3. 强连通分量:有向图中的极大强连通子图称做有向图的强连通分量。对于“极大”一词,我的理解是如果有向图中某部分是强连通的,再加入其它任何节点都将使得该部分变为非强连通,则此部分为原图的一个极大强连通子图

    算法作用

    可以将有向有环图转化为有向无环图(拓扑图-DAG图)。使得原始图具有拓扑图的种种性质,例如在拓扑图中使用递推可以在线性时间内解决一些最短路(最长路问题)

    算法思想

    缩点:Tarjan将有环图转化为无环图就是将每一个(scc)看作一个点,即所谓的缩点。但缩点并非特意需要代码实现,只是一种概念上的存在,原图在存在形式上仍为一个个独立的点,但我们可以根据不同点的(scc_id)值,人为将这些点进行分类,从而将具有相同属性的点划分为一个集合,即实现了缩点操作。

    进行一次Tarjan要达成的目标是:1.确定图上任意一点所属的scc编号 2.确定每个scc包含节点的个数

    Tarjan求解强连通分量采用了(DFS)的搜索顺序,搜索到每一个点时都有一个时间,采用(dfn[i])表示搜索到节点(i)时的时间
    在一个(scc)中,一定有一个(dfn)最小的点,即最先被搜素到的点,该(scc)中除该点以外的点通过环路可以走到的最小(dfn)编号记为(low[i])

    Tarjan求scc的过程因为使用栈,存在递归和回溯,所以较难说清它的思考流程,只能是参照代码,理解其中各个数组的含义,学会使用即可。

    Tarjan求解强连通分量主要依托的就是以上两个标记数组。我们思考在图的搜索过程中,如何判断出现了环路。在DFS的搜索过程中,每当我们搜索到一点,就会将该点入栈保存现场进行递归搜索其子节点,如果我们在搜索过程中遇到了一个栈中的节点,此时就形成了一个环路。使用栈仅能够判断是否存在环路,无法达成目标,(dfn)(low)的作用是标记一个节点被搜索到的顺序和它在scc中实际能走到的点的最小编号,如果搜索节点b的相邻节点时,遇到了栈中的节点a,一定满足(dfn[a] < dfn[b]),low[b]也应当由dfn[b]更新为dfn[a],这类节点满足(dfn != low)。如果某个节点c的(dfn == low),说明从它不能走到编号更小的节点了,即它就是一个scc中编号最小的节点,同属于该scc的节点因为不满足(dfn == low),所以都仍处于栈中,此时开始弹栈直到将c弹出,这些弹出的即为同一个scc中的节点,弹出时标记其所属scc编号和累加对应scc节点个数计数器即可

    (DFS)搜索需要用到栈(stk)
    (in_stk[i])用来标记点(i)当前是否处于栈中
    (Size[i])用来记录编号为(i)(scc)中点的数量

    语言描述起来有些混乱,B站有个讲的不错的视频见下

    代码实现

    代码中else if (in_stk[j]) low[u] = min(low[u], dfn[j]);,为什么用(dfn[j])而非(low[j])还尚不清楚,在Tarjan's SCC : example showing necessity of lowlink definition and calculation rule?中给出的理由是使用(low[j])就会破坏了(low)数组的含义,但我觉得恰恰相反,所以这个问题待定

    void tarjan(int u)
    {
        dfn[u] = low[u] = ++ timestamp;
        stk.push(u), in_stk[u] = true;
    
        for (int i = h[u]; ~i; i = ne[i])
        {
            int j = e[i];
            if (!dfn[j])
            {
                tarjan(j);
                low[u] = min(low[j], low[u]);
            }
            else if (in_stk[j]) low[u] = min(low[u], dfn[j]);
        }
        if (dfn[u] == low[u])
        {
            do {
                int t = stk.top(); stk.pop();
                id[t] = scc_cnt;
                in_stk[t] = false;
                ++ Size[scc_cnt];
            } while (t != u);
    
            // ++ scc_cnt;
            // int t = stk.top(); stk.pop();
            // id[t] = scc_cnt;
            // in_stk[t] = false;
            // ++ Size[scc_cnt];
            // while (t != u)
            // {
            //     t = stk.top(); stk.pop();
            //     id[t] = scc_cnt;
            //     in_stk[t] = false;
            //     ++ Size[scc_cnt];
            // }
        }
    }
    
  • 相关阅读:
    get请求乱码情况
    write()和prinln()的区别?
    校验码实现
    下载图片代码并且解析乱码
    servlet下根据相对路径找资源
    url-pattern配置
    获取网站资源 getResourceAsStream
    Servlet线程安全性
    http1.1 协议响应方面参数
    HTTP1.1协议请求方面参数
  • 原文地址:https://www.cnblogs.com/G-H-Y/p/15260692.html
Copyright © 2011-2022 走看看