Tarjan算法详解
前言
Tarjan(塔杨)算法其实不是很复杂,对于图这一块而言,Tarjan算法还是很有作用的,咱们现在一起来看看这个算法是啥东西以及咋实现。
简介
tarjan算法最直接的作用就是求图的联通分量。啥是联通分量呢?请你出去搞清楚再进来,谢谢。(啥是图?别进来了)
这算法如同基石一般,在此基础上我会研究联通分量的作用,后话了。
如果有人可以做个gif图,形象展示一下tarjan算法的过程,那就很棒了,可惜我不会。
教学代码-有向图的强联通分量
咱们先来个教学篇,讲一下这个tarjan算法的实现流程。
先给你一张单向图:
首先我得描述这些关系:
vector<pair<int, int> >Relations;
Relations.push_back(make_pair(1, 3));
Relations.push_back(make_pair(1, 2));
Relations.push_back(make_pair(2, 4));
Relations.push_back(make_pair(3, 5));
Relations.push_back(make_pair(3, 4));
Relations.push_back(make_pair(4, 6));
Relations.push_back(make_pair(5, 6));
Relations.push_back(make_pair(4, 1));
接着看你喜欢了,我用邻接表保存这些单向关系
vector<int> rel1, rel2, rel3, rel4, rel5, rel6;
vector<int>* details[6] = {
&rel1, &rel2, &rel3, &rel4, &rel5, &rel6};
for (int i = 0; i < Relations.size(); ++i)
{
details[Relations[i].first-1]->push_back(Relations[i].second-1);
}
然后我再定义几个要维护的数组还有一个stack
int Vis[6] = {
0 }; //0:not visited 1:in stack 2:leave stack
int Dfn[6] = {
0 };
int Low[6] = {
0 };
int TimeStamp = 1;
stack<int> sk;
这个Vis数组就有趣了,0表示没有访问过,1表示还在stack中,2表示已经出stack
timeStamp表示全局时间,没事可以暂时不理解。
Dfn数组用来记录每次的timeStamp
Low数组就没那么省心了。每个节点都或多或少有一片以自己为根节点的树。Low就代表了,这棵树能触碰到的最小的Dfn,加上dfs的帮助,于是这个Low就可以一直上传上去。
我相信你肯定已经懵逼了,毕竟这样说,太晦涩难懂。我这么说吧:啥叫强联通呢?从一个节点出发,兜兜转转,最后回到了自己。tarjan算法就是一种基于深度优先的算法,从起始节点一直找下去,最后找到了自己,那咋知道找到自己了呢?肯定是深度优先算法在回退的时候要一层层的将“哦吼,我找到最开始那个节点啦”这件事情带回去。
这样一来你就清楚了,tarjan算法对一个节点,要干点啥了:
tarjan:
1.先将Vis数组的下标置为已经访问,但是还没出stack(1),同时将Dfn和Low维护一下(就按照timeStamp走就行),顺便将这个节点入栈;
2.开始处理每一个自己的关系指向。
2.1如果这个关系指向是Vis=2,也就是早就经历过入栈出栈,那就别管了,pass
2.2如果这个关系指向是Vis=0,那就对其执行Tarjan算法(对没错,深度优先嘛,就递归了),反正执行完成后你总得出来(出来以后真的有可能也成2了,但是没关系,这个时候变2,那也是OK的,至少和自己有关系)。等出来之后,就直接借此更新Low下标,棒
2.3如果这个关系指向是Vis=1,也就是说,已经访问过了,那就没必要继续递归,直接借此更新Low下标,棒
3.深度,这事儿已经在第二步都做完了,下面咱们得开始退出了。这里退出的时候要伴随着可能发生的pop(每次pop出来的都是一组强联通分量)。像节点6走了一圈发现没啥,就要出去,很显然6是强联通分量,于是6pop出去了,同时更新一些Vis=2,开心。
轮到5了,显然和6一样的下场。