图论:有向图的强连通分量,tarjan算法(上)。

目录:

一.有向图的强连通分量

二:tarjan算法


 一:
有向图的强连通分量scc(strongly connected components)
强连通分量针对有向图而言
1.连通分量:
对于一个有向图中的一些点,如果任意两点都能相互到达,则称这些点以及对应的边构成连通分量。


2.强连通:
在有向图G中,如果两个顶点u,v间有一条从u到v的有向路径,
同时还有一条从v到u的有向路径,则称两个顶点强连通


3.强连通图:
如果有向图任意两点都有相互可达的路径,称此图为强连通图


4.强连通分量:
 有向非强连通图的极大强连通子图,称为强连通分量。
指极大连通分量。即该联通分量中增加任何一个点都不能构成连通分量了。
“极大”意味着,把图划分为若干个强连通分量后,不存在两个强连通分量相互可达。


5.强连通分量作用:
可以通过使用求解强联通分量的方式将一个有向图缩点成有向无环图(DAG),也称为拓扑图。
缩点:指将强联通分量缩成一个点
其中一个好处是我们在求解最短路/最长路的时候可以使用递推的方式求解。


6.dfs生成树:
处理强连通分量的一个有力的工具是dfs生成树
在dfs时,每当通过某条边 e 访问到一个新节点 v ,就加入这个点和这条边,最后得到的便是dfs生成树


7.dfs生成树中的四条边:
(1)树枝边(x, y):x是y的父节点;
(2)前向边(x, y):x是y的祖先节点,因此树枝边是一种特殊的前向边;
 从某个点到它的某个子孙节点的边。这种边相当于提供某种“捷径”,
 在这个问题里不太重要,即使把它们全部删去,对于连通性也没什么影响。
(3)后(反)向边(x, y):y是x的祖先节点,并且从x还能回到y
 从某个点到它的某个祖先节点的边。这种边就是产生环的原因,
 如果删去所有反向边,那么原图会成为有向无环图。
(4)横叉边(x, y):从当前DFS路径上的点x连向已经搜索完毕的的点y的边。
 从某个点到一个既非它子孙节点、也非它祖先节点的边。这种边本身不产生环,
 但是它可能把两个强连通子图“连接”起来,形成一个更大的强连通子图。


8. DFS序:
 按照先序遍历,每次遇到新的节点就把新访问节点加到序列中
 反向边和横叉边都有一个特点:起点的dfs序必然大于终点的dfs序
 导出一个有用的结论:对于每个强连通分量,存在一个点是其他所有点的祖先
 把这个唯一的祖先节点称为强连通分量的根。显然,根是强连通分量中dfs序最小的节点。


 二:
 Tarjan算法:
 Tarjan 算法是基于深度优先搜索的,用于求解图的连通性问题的算法
 引入时间戳(dfs[])(从1开始计数)和追溯值(low[])的概念
 1.时间戳:
 在有向图的深度优先遍历中,记录每个点第一次被访问的时间的顺序,则这个顺序就是这个点的时间戳。
 用dfn[ ] 表示。 每个点的时间戳不一定,取决于从哪个点开始遍历。
 时间戳可以帮我们判断这个点是否已经遍历过,有vis的功能。


 2.追溯值:
 在有向图中,点x的追溯值为其子树满足以下条件的最小时间戳:
(1)该点已经访问过[ 有dfn值 ]
(2)存在一条从x出发的边以它为终点。


 3.算法原理:
 把low(p)定义为p所在子树的节点经过最多一条非树边u->v(其中v可达u)
 能到达的节点中最小的dfs序 根据这样的定义,某个点 p 是强连通分量的根,
 等价于 dfsn(p)=low(p)


 4.算法整体思路:
 low(p)可以通过动态规划得到,对于以某个点 p为起点的边 p->q : 
 (1) 如果 q 未访问过:
 则 q 在 p 所在的子树上,如果某节点 r 从 q 起可以经过最多一条反向边到达,
 则从 p 起也可以(先从 p 到 q ,再到 r ),于是先递归处理点 q ,
 然后令 low(p):=min(low(p),low(q))
 (2)如果 q 已访问过,且从 q 可以到达 p ,
 令 low(p):=min(low(p),dfsn(q))
 (3)如果 q 已访问过,且从 q 不能到达 p ,
 不做处理 (后两种情况都是非树边)


 确认一个点能不能到达另一个点呢?
 因为反向边和横叉边都指向dfs序较小的节点,
 而前向边的存在又不影响状态转移方程,
 所以我们只需要确认比该点dfs序小的哪些点能到达该点即可,
 这可以用一个栈动态地维护。
 栈:用于记录当前已经被访问但还未被归类于任意一强连通分量的结点
 每当搜索到新点,就令它入栈 在对点 p 的搜索结束时,
 如果 low(p)=n<dfsn(q) ,设dfs序为 n 的点为 q ,
 则 p 点可达的点 q 点都可达,考虑到对 q 点的搜索很可能还没有结束,
 所以 p 应当留在栈中 而如果发现 p 满足 low(p)=dfsn(p) ,
 则说明 p 是某个强连通分量的根,它和栈中的子孙节点相互可达 但同时,
 它和栈中的子孙节点也无法到达 p 的祖先节点,
 以及祖先节点其他尚未搜索的分支了,所以不断从栈顶弹出元素,
 直到弹出 p (注意这样维护的栈中节点的dfs序是单调增的),同时记录答案
 5.算法实现:
 

stack<int> stk;
  //instk:是否在栈中  scc:每个点所属的强连通分量编号  cscc:强连通分量的数量
int dfsn[MAXN], low[MAXN], instk[MAXN], scc[MAXN], cnt, cscc;
void tarjan(int p)
{
    low[p] = dfsn[p] = ++cnt;
    instk[p] = 1;
    stk.push(p); // 进栈
    for (auto q : edges[p])
    {
        if (!dfsn[q]) // 未访问过
        {
            tarjan(q); // 递归地搜索
            low[p] = min(low[p], low[q]);
        }
        else if (instk[q]) // 访问过,且q可达p
            low[p] = min(low[p], dfsn[q]);
    }
    if (low[p] == dfsn[p]) // 发现强连通分量的根
    {
        int top;
        cscc++;
        do
        {
            top = stk.top();
            stk.pop();
            instk[top] = 0;
            scc[top] = cscc; // 记录所属的强连通分量
        } while (top != p); // 直到弹出p才停止
    }
}


由于原图并不一定是强连通图,所以不能随便找一个点作为根dfs就完事,而要保证每个点都被dfs到:
 

for (int i = 1; i <= n; ++i)
if (!dfsn[i])
tarjan(i);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值