图论算法(6) --- Tarjan算法求强连通分量

注:此算法以有向图作为输入,并按照所在的强连通分量给出其顶点集的一个划分。graph中的每个节点只在一个强连通分量里出现,即使是单点。

任选一点开始进行深度优先搜索(若dfs结束后仍有未访问的节点,则再从中任选一点再从进行)。搜索过程中已访问的节点不再访问。搜索树的若干子树构成了图的强连通分量。

节点按照被访问的顺序存入栈中。从搜索树的子树返回至一个节点时,检查该节点是否是某一连通分量的根节点,并将其从栈中删除。如果某节点是强连通分量的根,则在它之前出栈且还不属于其他强连通分量的节点构成了该节点所在的强连通分量。

注:根节点的性质

算法的关键在于如何判定某节点是否是强连通分量的根。对于“强连通分量的根”这一说法仅针对此算法。事实上,强连通分量没有特定的“根”的。在这里根节点指深度优先搜索时强连通分量的节点构成了该节点所在的强连通分量。

为找到根结点,我们给每个结点v一个深度优先搜索标号v.index,表示它是第几个被访问的结点。此外,每个结点v还有一个值v.lowlink,表示从v出发经有向边可到达的所有结点中最小的index。显然v.lowlink总是不大于v.index,且当从v出发经有向边不能到达其他结点时,这两个值相等。v.lowlink在深度优先搜索的过程中求得,v是强连通分量的根当且仅当v.lowlink = v.index。

algorithm tarjan is
  input: 图 G = (V, E)
  output: 以所在的强连通分量划分的顶点集

  index := 0
  S := empty    // 置栈为空
  for each v in V do
    if (v.index is undefined)
      strongconnect(v)
    end if

  function strongconnect(v)
    // 将未使用的最小index值作为结点v的index
    v.index := index
    v.lowlink := index
    index := index + 1
    S.push(v)

    // 考虑v的后继结点
    for each (v, w) in E do
      if (w.index is undefined) then
        // 后继结点w未访问,递归调用
        strongconnect(w)
        v.lowlink := min(v.lowlink, w.lowlink)
      else if (w is in S) then
        // w已在栈S中,亦即在当前强连通分量中
        v.lowlink := min(v.lowlink, w.index)
      end if

    // 若v是根则出栈,并求得一个强连通分量
    if (v.lowlink = v.index) then
      start a new strongly connected component
      repeat
        w := S.pop()
        add w to current strongly connected component
      until (w = v)
      output the current strongly connected component
    end if
  end function

变量index是深度优先搜索的结点计数器。S是栈,初始为空,用于存储已经访问但未被判定属于任一强连通分量的结点。注意这并非一个一般深度优先搜索的栈,结点不是在以它为根的子树搜索完成后出栈,而是在整个强连通分量被找到时。

最外层循环用于查找未访问的结点,以保证所有结点最终都会被访问。strongconnect进行一次深度优先搜索,并找到结点v的后继结点构成的子图中所有的强连通分量。

当一个结点完成递归时,若它的lowlink仍等于index,那么它就是强连通分量的根。算法将在此结点之后入栈(包含此结点)且仍在栈中的结点出栈,并作为一个强连通分量输出。

备注:

1.复杂度:对每个结点,过程strongconnect只被调用一次;整个程序中每条边最多被考虑两次。因此算法的运行时间关于图的边数是线性的,即O(|V|+|E|)。

2.判断结点v‘是否在栈中应在常数时间内完成,例如可以对每个结点保存一个是否在栈中的标记。

3.同一个强连通分量内的结点是无序的,但此算法具有如下性质:每个强连通分量都是在它的所有后继强连通分量被求出之后求得的。因此,如果将同一强连通分量收缩为一个结点而构成一个有向无环图,这些强连通分量被求出的顺序是这一新图的拓扑序的逆序。

下面附上实现源代码:

/*
 * Author : YANG Xiangyu
 *
 * Hint: tarjan scc
 *
 * The Chinese University of Hong Kong
 */
package cyclefinder;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;

public class TarjanSCC{
	class Node{//内部类定义节点
		Vector<Integer> adj;
	}
	Node[] graph;
	Stack<Integer> stack;
	int indices;
	boolean[] inStack;
	int[] index;
	int[] lowLink;
	int[] component;
	Set<Integer> nodeset;
	int numComponents;
	int n,m;
	public TarjanSCC(){
		graph=new Node[16000];
		inStack=new boolean[16000];
		index=new int[16000];
		lowLink=new int[16000];
		component=new int[16000];
		inStack=new boolean[16000];
		stack=new Stack<Integer>();
		nodeset=new HashSet<Integer>();
		for(int i=0;i<component.length;++i){
			component[i]=0xffffffff;
		}
		for(int i=0;i<graph.length;++i){
			graph[i]=new Node();
			graph[i].adj=new Vector<Integer>();
		}
	}

	public void strongconnect(int v){
		index[v]=indices;
		lowLink[v]=indices;
		indices++;
		stack.push(v);
		inStack[v]=true;
		for(int j=0;j<graph[v].adj.size();++j){
			int w=graph[v].adj.get(j);
			if(index[w]==0){
				strongconnect(w);
				lowLink[v]=Math.min(lowLink[v],lowLink[w]);
			}else if(inStack[w]){
				lowLink[v]=Math.min(lowLink[v],index[w]);
			}
		}
		if(lowLink[v]==index[v]){
			int w=0;
			do{
				w=stack.pop();
				component[w]=numComponents;
				inStack[w]=false;
			}while(v!=w&&!stack.empty());
			numComponents++;
		}
	}
	public void tarjanFunc(){
		indices=1;
		while(!stack.empty()){
			stack.pop();
		}
		Object[] v=nodeset.toArray();
		for(int i=0;i<v.length;++i){
			lowLink[(int) v[i]]=0;
			index[(int) v[i]]=0;
			inStack[(int) v[i]]=false;
		}
		numComponents=0;
		for(int i=0;i<v.length;++i){
			if(index[(int) v[i]]==0){
				strongconnect((int) v[i]);
			}
		}
	}

	public void input(String path) throws FileNotFoundException{
		Scanner input=new Scanner(new File(path));
		n=input.nextInt();
		m=input.nextInt();
		int a=0,b=0;
		for(int i=0;i<m;++i){
			a=input.nextInt();
			b=input.nextInt();
			graph[a].adj.add(b);
			nodeset.add(a);
		}
		input.close();
	}
	public void prints(String path) throws FileNotFoundException
	{
		PrintStream output = new PrintStream(path);
		for(int i=0;i<16000;++i){
			output.print(component[i]+" "+i+" ");
			for(int j=0;j<graph[i].adj.size();++j){
				output.print(graph[i].adj.get(j)+" ");
			}
			output.println();
		}
		System.out.println(numComponents);
		output.println(numComponents);
		output.close();
	}
	public static void main(String[] args) throws FileNotFoundException{
		TarjanSCC tarjanscc=new TarjanSCC();
		tarjanscc.input("C:/circleOrderedId.txt");
		tarjanscc.tarjanFunc();
		tarjanscc.prints("C:/tarjansccresults.txt");
	}
}
时间: 2024-10-29 19:08:18

图论算法(6) --- Tarjan算法求强连通分量的相关文章

tarjan算法+缩点:求强连通分量 POJ 2186

强连通分量:1309. [HAOI2006]受欢迎的牛 ★★   输入文件:cow.in   输出文件:cow.out   简单对比时间限制:1 s   内存限制:128 MB [题目描述] 每一头牛的愿望就是变成一头最受欢迎的牛.现在有N头牛,给你M对整数(A,B),表示牛 A 认为牛 B受欢迎.这种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也认为牛C受欢迎.你的任务是求出有多少头牛被所有的牛认为是受欢迎的. [输入格式] 第1行两个整数N,M: 接下来M行,每行两个数A

HDU1269迷宫城堡(裸Tarjan有向图求强连通分量个数)

迷宫城堡Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 13833    Accepted Submission(s): 6174 Problem Description 为了训练小希的方向感,Gardon建立了一座大城堡,里面有N个房间(N<=10000)和M条通道(M<=100000),每个通道都是单向的,就是说若称某通道连通了A房

图论算法(6)(更新版) --- Tarjan算法求强连通分量

之前Tarjan算法求强连通分量博文中,代码实现用到了固定大小数组,扩展起来似乎并不是很方便,在java里这样来实现本身就是不太妥当的,所以下面给出一个更新版本的代码实现: package test; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util

转 tarjan算法求强连通分量

无意中想起图的强连通分量来,之前也一直想写所以今天决定来填这个坑.PS:由于本人比较懒,之前做过一个讲解的PPT,不过那是好遥远之前,年代已久早已失传,所以本文里的图来自网络.以后周末都用来填坑也挺好. ------------------------------分割线----------------------------------------- 在有向图G中,如果两个顶点间至少存在一条路径,那么这两个顶点就是强连通(strongly connected). 如果有向图G的每两个顶点都强连通

[图论] 有向图强连通分量 (kosaraju算法,Tarjan算法)

记录自己的想法:在有向图中,如果一些顶点中任意两个顶点都能互相到达(间接或直接),那么这些顶点就构成了一个强连通分量,如果一个顶点没有出度,即它不能到达其他任何顶点,那么该顶点自己就是一个强连通分量.在用kosaraju算法和Tarjan算法求强连通分量的时候,就是给所有的顶点分组染色,同一种颜色的顶点在同一个强连通分量中,记录有多少种颜色(有多少个强联通分量),每个顶点属于哪种颜色(每个顶点在哪个强连通分量重).在同一个强连通分量中的所有顶点可以缩为一个顶点,然后根据缩点构造DAG(有向无环图

poj1236 Network of Schools ,求强连通分量(Tarjan算法),缩点

题目链接: 点击打开链接 题意: 给定一个有向图,求: 1) 至少要选几个顶点,才能做到从这些顶点出发,可以到达全部顶点 2) 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部顶点 顶点数<= 100 求完强连通分量后,缩点,计算每个点的入度,出度. 第一问的答案就是入度为零的点的个数, 第二问就是max(n,m) // 入度为零的个数为n, 出度为零的个数为m. //kuangbin巨巨分析很棒! #include<cstdio> #include<cstring>

Tarjan 算法求 LCA / Tarjan 算法求强连通分量

[时光蒸汽喵带你做专题]最近公共祖先 LCA (Lowest Common Ancestors)_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili tarjan LCA - YouTube Tarjan算法_LCA - A_Bo的博客 - CSDN博客 Tarjan离线算法求最近公共祖先(LCA) - 初学者 - CSDN博客 最近公共祖先(LCA) - riteme.site Fuzhou University OnlineJudge P3379 [模板]最近公共祖先(LCA) - 洛谷 |

POJ-3180 The Cow Prom(tarjan求强连通分量)

题目链接:http://poj.org/problem?id=3180 题目大意:求一个有向图的强连通分量 算法:求强连通分量首选tarjan算法 这里简单说一下tarjan的思路 时间戳是什么:在搜索时访问的最早时间 维护dfn[u]表示u的时间戳 low[u]表示u点所能回到的最早的祖先的时间戳 开一个栈,把搜索的点入栈.搜索时遇到已经搜过的点,取low[u]和dfn[v]的最小值,回溯时取low[u]和low[v]的最小值(标记上传)传到dfn[u]<=low[u]时表示已经回溯到最上面的

CCF 高速公路 tarjan求强连通分量

问题描述 某国有n个城市,为了使得城市间的交通更便利,该国国王打算在城市之间修一些高速公路,由于经费限制,国王打算第一阶段先在部分城市之间修一些单向的高速公路. 现在,大臣们帮国王拟了一个修高速公路的计划.看了计划后,国王发现,有些城市之间可以通过高速公路直接(不经过其他城市)或间接(经过一个或多个其他城市)到达,而有的却不能.如果城市A可以通过高速公路到达城市B,而且城市B也可以通过高速公路到达城市A,则这两个城市被称为便利城市对. 国王想知道,在大臣们给他的计划中,有多少个便利城市对. 输入

poj 2186 tarjan求强连通分量

蕾姐讲过的例题..玩了两天后才想起来做 貌似省赛之后确实变得好懒了...再努力两天就可以去北京玩了! 顺便借这个题记录一下求强连通分量的算法 1 只需要一次dfs 依靠stack来实现的tarjan算法 每次走到一个点 马上把它压入栈中 每次对与这个点相连的点处理完毕 判断是否low[u]==dfn[u] 若是 开始退栈 直到栈顶元素等于u才退出(当栈顶元素等于u也需要pop) 每次一起退栈的点属于同一个强连通分量 储存图可以用链式前向星也可以用邻接矩阵更可以用vector 蕾姐说不会超时 我信