Tarjan's algorithm
Tarjan算法可以用来求有向图的强连通分量个数,之前十分粗略的写了Kosaraju算法,这里打算比较认真的分析一下Tarjan算法,然后给出算法实现代码。
Tarjan算法的主要算法部分也是dfs(深度优先搜索),但利用了重要的额外信息。下面详细分析一下算法执行过程。
再强调一个强连通子图的重要特点:对于强连通子图,有一个特定的事实就是,该子图一定形成环,那么从该子图中任意点出发,总能回到出发点。
基于上面这一点,Tarjan算法通过维护两个存放顶点访问顺序(时间)的数组。如果子图形成环,则将处于环中的每一个顶点的访问顺序置为该环的出发点的访问时间,以表明他们是一个强连通子图。可能你会怀疑进入环后,不会只在环中遍历,可能会跳到其他顶点上。实际上这担心是多余,因为图结构使用邻接链表表示,强连通子图使用dfs进行遍历时,只会寻找与当前顶点连接的出度顶点,而形成环的子图中,会很合理的按顺序遍历完。对于孤立点,则自身就是一个环,即强连通分量。
这就是Tarjan算法的思想,主要的就是维护的两个存储访问顺序的数组,然后,形成环的节点的访问时间都置为该强连通子图的出发点的访问时间。
通过下图可以更直观的理解Tarjan算法的执行过程(图来自维基):
时间复杂度分析:
最坏情况是图G的强连通子图就是其本身(这样的图称为强连通图),这时dfs的消费为 $ O(|V| + |E|) $,最后一次dfs的while循环再消费掉 $ O(V) $,所以dfs()最坏情况为 $ O(|V| + |E|) $。最后tarjan()的总消耗为 $ O (V^2) $。
空间复杂度:
显然 $ O(V) $。
算法实现:
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <stack>
#include <list>
#include <minmax.h>
using namespace std;
const int N = 10010;
list<int> *adj;
stack<int> s;
int vis[N], low[N];
bool onstack[N];
int times = 0, scc = 0;
void addEdge(int u, int v)
{
adj[u].push_back(v);
}
void dfs(int u)
{
vis[u] = low[u] = times++;
s.push(u);
onstack[u] = true;
for (list<int>::iterator i = adj[u].begin(); i != adj[u].end(); ++i)
{
int v = *i;
if (vis[v] == -1)
{
dfs(v);
low[u] = min(low[u], low[v]);
}
else if (onstack[v] == true)
low[u] = min(low[u], vis[v]);
}
if (low[u] == vis[u])
{
while (s.top() != u)
{
int w = s.top();
cout << w << ' ';
onstack[w] = false;
s.pop();
}
int w = s.top();
cout << w << endl;
onstack[w] = false;
s.pop();
scc++;
}
}
void tarjan(int V, int E)
{
adj = new list<int>[V + 1];
list<int> v;
for (int i = 1; i <= V; i++)
{
vis[i] = low[i] = -1;
onstack[i] = false;
}
for (int i = 1; i <= E; i++)
{
int u, w;
cin >> u >> w;
adj[u].push_back(w);
}
for (int i = 1; i <= V; i++)
if (vis[i] == -1)
dfs(i);
cout << "该图的强连通分量个数为:" << scc << endl;
}
int main(int argc, char **argv)
{
int V, E;
cin >> V >> E;
tarjan(V, E);
return 0;
}
代码中dfs()函数的for循环后面的部分用来输出所有强连通子图中的顶点,并求出scc(Strongly Connected Components)个数。
下面以前面wiki图为例测试一下算法。
算法测试结果:
3 2 1
7 6
5 4
8
该图的强连通分量个数为:4
参考:
1.https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
2.https://www.geeksforgeeks.org/tarjan-algorithm-find-strongly-connected-components/
Tarjan's algorithm的更多相关文章
- algorithm@ Strongly Connected Component
Strongly Connected Components A directed graph is strongly connected if there is a path between all ...
- Prim 最小生成树算法
Prim 算法是一种解决最小生成树问题(Minimum Spanning Tree)的算法.和 Kruskal 算法类似,Prim 算法的设计也是基于贪心算法(Greedy algorithm). P ...
- Kruskal 最小生成树算法
对于一个给定的连通的无向图 G = (V, E),希望找到一个无回路的子集 T,T 是 E 的子集,它连接了所有的顶点,且其权值之和为最小. 因为 T 无回路且连接所有的顶点,所以它必然是一棵树,称为 ...
- Kosaraju 算法检测有向图的强连通性
给定一个有向图 G = (V, E) ,对于任意一对顶点 u 和 v,有 u --> v 和 v --> u,亦即,顶点 u 和 v 是互相可达的,则说明该图 G 是强连通的(Strong ...
- Kosaraju 算法查找强连通分支
有向图 G = (V, E) 的一个强连通分支(SCC:Strongly Connected Components)是一个最大的顶点集合 C,C 是 V 的子集,对于 C 中的每一对顶点 u 和 v, ...
- GO语言的开源库
Indexes and search engines These sites provide indexes and search engines for Go packages: godoc.org ...
- 210. Course Schedule II
题目: There are a total of n courses you have to take, labeled from 0 to n - 1. Some courses may have ...
- Go语言(golang)开源项目大全
转http://www.open-open.com/lib/view/open1396063913278.html内容目录Astronomy构建工具缓存云计算命令行选项解析器命令行工具压缩配置文件解析 ...
- [转]Go语言(golang)开源项目大全
内容目录 Astronomy 构建工具 缓存 云计算 命令行选项解析器 命令行工具 压缩 配置文件解析器 控制台用户界面 加密 数据处理 数据结构 数据库和存储 开发工具 分布式/网格计算 文档 编辑 ...
随机推荐
- JS高级---函数声明和函数表达式的区别
函数声明和函数表达式的区别 多用函数表达式 var ff=function(){}; //函数声明 // // if(true){ // function f1() { // console.log( ...
- html5 标准文档结构
<!DOCTYPE html> <html lang="zh-cmn-Hans"> <head> <meta charset=" ...
- ubuntu---yolo报错darknet: ./src/cuda.c:36: check_error: Assertion `0' failed.
装好darknet后,直接测试的时候,报错: darknet: ./src/cuda.c:36: check_error: Assertion `0' failed.解决办法是打开yolov3.cfg ...
- java判断相等
一.字符串 1.equals():比较内容,推荐 String a=new String("abc"); String b=new String("abc"); ...
- 题解【洛谷P1807】最长路_NOI导刊2010提高(07)
题面 题解 最长路模板. 只需要在最短路的模板上把符号改一下\(+\)初值赋为\(-1\)即可. 注意一定是单向边,不然出现了正环就没有最长路了,就好比出现了负环就没有最短路了. 只能用\(SPFA\ ...
- IIR filter design from analog filter
Analog filter和digital filter的联系: z变换与Laplace从数学上的关系为: 但这种关系在实际应用上不好实现,因此通常使用biliner transform(https: ...
- java is 和 == ,以及equal
package string; public class MemAddrChange { public static void main(String[] args) { // const 常量区,
- kbhit函数说明
kbhit,用来检测键盘是否被敲击.所以就有了他的名字:keyboard hit 当键盘有按键被按下时,kbhit函数就会返回一个非0值. 当我们在写某个程序时,希望读入一些字符,但是又不能停在那里只 ...
- 10day rpm简单用法qa ql qf "`"用法
查看软件是否安装: [root@oldboyedu ~]# rpm -qa sl -q表示查询 -a表示所有 sl-5.02-1.el7.x86_64 查看软件包中有哪些信息 [root@oldboy ...
- 跨平台C++ IDE
参考博客:https://blog.csdn.net/m0_37314675/article/details/77881287