图之强连通、强连通图、强连通分量 Tarjan算法

强连通分量

简介

在阅读下列内容之前,请务必了解图论基础部分。

强连通的定义是:有向图 G 强连通是指,G 中任意两个结点连通。

强连通分量(Strongly Connected Components,SCC)的定义是:极大的强连通子图。

这里想要介绍的是如何来求强连通分量。

Tarjan 算法

Robert E. Tarjan (1948~) 美国人。

Tarjan 发明了很多算法结构。光 Tarjan 算法就有很多,比如求各种联通分量的 Tarjan 算法,求 LCA(Lowest Common Ancestor,最近公共祖先)的 Tarjan 算法。并查集、Splay、Toptree 也是 Tarjan 发明的。

我们这里要介绍的是在有向图中求强连通分量的 Tarjan 算法。

另外,Tarjan 的名字 j 不发音,中文译为塔扬。

DFS 生成树

在介绍该算法之前,先来了解 DFS 生成树 ,我们以下面的有向图为例:

有向图的 DFS 生成树主要有 4 种边(不一定全部出现):

  1. 树边(tree edge):绿色边,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。
  2. 反祖边(back edge):黄色边,也被叫做回边,即指向祖先结点的边。
  3. 横叉边(cross edge):红色边,它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点 并不是 当前结点的祖先时形成的。
  4. 前向边(forward edge):蓝色边,它是在搜索的时候遇到子树中的结点的时候形成的。

我们考虑 DFS 生成树与强连通分量之间的关系。

如果结点u是某个强连通分量在搜索树中遇到的第一个结点,那么这个强连通分量的其余结点肯定是在搜索树中以u为根的子树中。u被称为这个强连通分量的根。

反证法:假设有个结点v在该强连通分量中但是不在以u为根的子树中,那么u到v的路径中肯定有一条离开子树的边。但是这样的边只可能是横叉边或者反祖边,然而这两条边都要求指向的结点已经被访问过了,这就和u是第一个访问的结点矛盾了。得证。

Tarjan 算法求强连通分量

在 Tarjan 算法中为每个结点u维护了以下几个变量:

  1. DFN[u]:深度优先搜索遍历时结点 被搜索的次序。
  2. LOW[u]:设以u为根的子树为Subtree(u) 。 LOW[u]定义为以下结点的 的最小值:Subtree(u)中的结点;从Subtree(u)通过一条不在搜索树上的边能到达的结点。

一个结点的子树内结点的 DFN 都大于该结点的 DFN。

从根开始的一条路径上的 DFN 严格递增,LOW 严格非降。

按照深度优先搜索算法搜索的次序对图中所有的结点进行搜索。在搜索过程中,对于结点 和与其相邻的结点 (v 不是 u 的父节点)考虑 3 种情况:

  1. v未被访问:继续对v进行深度搜索。在回溯过程中,用LOW[v]更新LOW[u]。因为存在从u到v的直接路径,所以v能够回溯到的已经在栈中的结点,u也一定能够回溯到。
  2. v被访问过,已经在栈中:即已经被访问过,根据LOW值的定义(能够回溯到的最早的已经在栈中的结点),则用DFN[v]更新LOW[u]。
  3. v被访问过,已不在在栈中:说明v已搜索完毕,其所在连通分量已被处理,所以不用对其做操作。

将上述算法写成伪代码:

 1 TARJAN_SEARCH(int u)
 2     vis[u]=true
 3     low[u]=dfn[u]=++dfncnt
 4     push u to the stack
 5     for each (u,v) then do
 6         if v hasn‘t been search then
 7             TARJAN_SEARCH(v) // 搜索
 8             low[u]=min(low[u],low[v])// 回溯
 9         else if v has been in the stack then
10             low[u]=min(low[u],dfn[v])

对于一个连通分量图,我们很容易想到,在该连通图中有且仅有一个DFN[u]=LOW[u] 。该结点一定是在深度遍历的过程中,该连通分量中第一个被访问过的结点,因为它的 DFN 值和 LOW 值最小,不会被该连通分量中的其他结点所影响。

因此,在回溯的过程中,判定DFN[u]=LOW[u]的条件是否成立,如果成立,则栈中从u后面的结点构成一个 SCC。

实现

 1 int dfn[N], low[N], dfncnt, s[N], tp;
 2 int scc[N], sc;  // 结点 i 所在 scc 的编号
 3 int sz[N];       // 强连通 i 的大小
 4 void tarjan(int u) {
 5   low[u] = dfn[u] = ++dfncnt, s[++tp] = u;
 6   for (int i = h[u]; i; i = e[i].nex) {
 7     const int &v = e[i].t;
 8     if (!dfn[v])
 9       tarjan(v), low[u] = min(low[u], low[v]);
10     else if (!scc[v])
11       low[u] = min(low[u], dfn[v]);
12   }
13   if (dfn[u] == low[u]) {
14     ++sc;
15     while (s[tp] != u) scc[s[tp]] = sc, sz[sc]++, --tp;
16     scc[s[tp]] = sc, sz[sc]++, --tp;
17   }
18 }

时间复杂度O(n+m) 。

Kosaraju 算法

Kosaraju 算法依靠两次简单的 DFS 实现。

第一次 DFS,选取任意顶点作为起点,遍历所有为访问过的顶点,并在回溯之前给顶点编号,也就是后序遍历。

第二次 DFS,对于反向后的图,以标号最大的顶点作为起点开始 DFS。这样遍历到的顶点集合就是一个强连通分量。对于所有未访问过的结点,选取标号最大的,重复上述过程。

两次 DFS 结束后,强连通分量就找出来了,Kosaraju 算法的时间复杂度为O(n+m)。

实现

 1 // g 是原图,g2 是反图
 2
 3 void dfs1(int u) {
 4   vis[u] = true;
 5   for (int v : g[u])
 6     if (!vis[v]) dfs1(v);
 7   s.push_back(v);
 8 }
 9
10 void dfs2(int u) {
11   color[u] = sccCnt;
12   for (int v : g2[u])
13     if (!color[v]) dfs2(v);
14 }
15
16 void kosaraju() {
17   sccCnt = 0;
18   for (int i = 1; i <= n; ++i)
19     if (!vis[i]) dfs1(i);
20   for (int i = n; i >= 1; --i)
21     if (!color[s[i]]) {
22       ++sccCnt;
23       dfs2(s[i])
24     }
25 }

Garbow 算法

应用

我们可以将一张图的每个强连通分量都缩成一个点。

然后这张图会变成一个 DAG(为什么?)。

DAG 好啊,能拓扑排序了就能做很多事情了。

举个简单的例子,求一条路径,可以经过重复结点,要求经过的不同结点数量最多。

推荐题目

USACO Fall/HAOI 2006 受欢迎的牛

POJ1236 Network of Schools

接下来我们讨论一下Tarjan算法能够干一些什么:

既然我们知道,Tarjan算法相当于在一个有向图中找有向环,那么我们Tarjan算法最直接的能力就是缩点辣!缩点基于一种染色实现,我们在Dfs的过程中,尝试把属于同一个强连通分量的点都染成一个颜色,那么同一个颜色的点,就相当于一个点。比如刚才的实例图中缩点之后就可以变成这样:

将一个有向带环图变成了一个有向无环图(DAG图)。很多算法要基于有向无环图才能进行的算法就需要使用Tarjan算法实现染色缩点,建一个DAG图然后再进行算法处理。在这种场合,Tarjan算法就有了很大的用武之地辣!

那么这个时候 ,我们再引入一个数组color【i】表示节点i的颜色,再引入一个数组stack【】实现一个栈,然后在Dfs过程中每一次遇到点都将点入栈,在每一次遇到关键点的时候将栈内元素弹出,一直弹到栈顶元素是关键点的时候为止,对这些弹出来的元素进行染色即可。

 1 void Tarjan(int u)//此代码仅供参考
 2 {
 3     vis[u]=1;
 4     low[u]=dfn[u]=cnt++;
 5     stack[++tt]=u;
 6     for(int i=0;i<mp[u].size();i++)
 7     {
 8         int v=mp[u][i];
 9         if(vis[v]==0)Tarjan(v);
10         if(vis[v]==1)low[u]=min(low[u],low[v]);
11     }
12     if(dfn[u]==low[u])
13     {
14         sig++;
15         do
16         {
17             low[stack[tt]]=sig;
18             color[stack[tt]]=sig;
19             vis[stack[tt]]=-1;
20         }
21         while(stack[tt--]!=u);
22     }
23 }


原文:https://blog.csdn.net/justlovetao/article/details/6673602

有向图强连通分量的Tarjan算法 [有向图强连通分量]

在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。

下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。

直接根据定义,用双向遍历取交集的方法求强连通分量,时间复杂度为O(N^2+M)。更好的方法是Kosaraju算法或Tarjan算法,两者的时间复杂度都是O(N+M)。本文介绍的是Tarjan算法。 [Tarjan算法]

Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。由定义可以得出,

1 Low(u)=Min
2 {
3    DFN(u),
4    Low(v),(u,v)为树枝边,u为v的父节点
5    DFN(v),(u,v)为指向栈中节点的后向边(非横叉边)
6 }

当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。

算法伪代码如下

 1 tarjan(u)
 2 {
 3     DFN[u]=Low[u]=++Index                      // 为节点u设定次序编号和Low初值
 4     Stack.push(u)                              // 将节点u压入栈中
 5     for each (u, v) in E                       // 枚举每一条边
 6         if (v is not visted)               // 如果节点v未被访问过
 7             tarjan(v)                  // 继续向下找
 8             Low[u] = min(Low[u], Low[v])
 9         else if (v in S)                   // 如果节点v还在栈内
10             Low[u] = min(Low[u], DFN[v])
11     if (DFN[u] == Low[u])                      // 如果节点u是强连通分量的根
12         repeat
13             v = S.pop                  // 将v退栈,为该强连通分量中一个顶点
14             print v
15         until (u== v)
16 }

接下来是对算法流程的演示。

从节点1开始DFS,把遍历到的节点加入栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。

返回节点5,发现DFN[5]=LOW[5],退栈后{5}为一个强连通分量。

返回节点3,继续搜索到节点4,把4加入堆栈。发现节点4向节点1有后向边,节点1还在栈中,所以LOW[4]=1。节点6已经出栈,(4,6)是横叉边,返回3,(3,4)为树枝边,所以LOW[3]=LOW[4]=1。

继续回到节点1,最后访问节点2。访问边(2,4),4还在栈中,所以LOW[2]=DFN[4]=5。返回1后,发现DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量{1,3,4,2}。

至此,算法结束。经过该算法,求出了图中全部的三个强连通分量{1,3,4,2},{5},{6}。

可以发现,运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(N+M)。

求有向图的强连通分量还有一个强有力的算法,为Kosaraju算法。Kosaraju是基于对有向图及其逆图两次DFS的方法,其时间复杂度也是 O(N+M)。与Trajan算法相比,Kosaraju算法可能会稍微更直观一些。但是Tarjan只用对原图进行一次DFS,不用建立逆图,更简洁。在实际的测试中,Tarjan算法的运行效率也比Kosaraju算法高30%左右。此外,该Tarjan算法与求无向图的双连通分量(割点、桥)的Tarjan算法也有着很深的联系。学习该Tarjan算法,也有助于深入理解求双连通分量的Tarjan算法,两者可以类比、组合理解。

求有向图的强连通分量的Tarjan算法是以其发明者Robert Tarjan命名的。Robert Tarjan还发明了求双连通分量的Tarjan算法,以及求最近公共祖先的离线Tarjan算法,在此对Tarjan表示崇高的敬意。

附:tarjan算法的C++程序

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 using namespace std;
 5 #define N 100
 6 #define M 100
 7 struct Edge
 8 {
 9     int v;
10     int next;
11 };
12 Edge edge[M];//边的集合
13
14 int node[N];//顶点集合
15 int instack[N];//标记是否在stack中
16 int stack[N];
17 int Belong[N];//各顶点属于哪个强连通分量
18 int DFN[N];//节点u搜索的序号(时间戳)
19 int LOW[N];//u或u的子树能够追溯到的最早的栈中节点的序号(时间戳)
20 int n, m;//n:点的个数;m:边的条数
21 int cnt_edge;//边的计数器
22 int Index;//序号(时间戳)
23 int top;
24 int Bcnt;//有多少个强连通分量
25
26 void add_edge(int u, int v)//邻接表存储
27 {
28     edge[cnt_edge].next = node[u];
29     edge[cnt_edge].v = v;
30     node[u] = cnt_edge++;
31 }
32 void tarjan(int u)
33 {
34     int i,j;
35     int v;
36     DFN[u]=LOW[u]=++Index;
37     instack[u]=true;
38     stack[++top]=u;
39     for (i = node[u]; i != -1; i = edge[i].next)
40     {
41         v=edge[i].v;
42         if (!DFN[v])//如果点v没被访问
43         {
44             tarjan(v);
45             if (LOW[v]<LOW[u])
46                 LOW[u]=LOW[v];
47         }
48         else//如果点v已经被访问过
49             if (instack[v] && DFN[v]<LOW[u])
50                 LOW[u]=DFN[v];
51     }
52     if (DFN[u]==LOW[u])
53     {
54         Bcnt++;
55         do
56         {
57             j=stack[top--];
58             instack[j]=false;
59             Belong[j]=Bcnt;
60         }
61         while (j!=u);
62     }
63 }
64 void solve()
65 {
66     int i;
67     top=Bcnt=Index=0;
68     memset(DFN,0,sizeof(DFN));
69     memset(LOW,0,sizeof(LOW));
70     for (i=1;i<=n;i++)
71         if (!DFN[i])
72             tarjan(i);
73 }
74 int main()
75 {
76     freopen("in.txt","r",stdin);
77     int i,j,k;
78     cnt_edge=0;
79     memset(node,-1,sizeof(node));
80     scanf("%d%d",&n,&m);
81     for(i=1;i<=m;i++)
82     {
83         scanf("%d%d",&j,&k);
84         add_edge(j,k);
85     }
86     solve();
87     for(i=1;i<=n;i++)
88         printf("%d ",Belong[i]);
89 }
90  

我自己根据模板写的适合我用的(可以忽略)

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <algorithm>
 4 #include <stack>
 5 using namespace std;
 6 #define N 100
 7 #define M 100
 8
 9 struct Edge{
10     int v;
11     int next;
12 }Edge[M];//边的集合
13
14 int node[N];//顶点集合
15 int instack[N];//标记是否在stack中
16 int Belong[N];//各顶点属于哪个强连通分量
17 int DFN[N];//节点u搜索的序号(时间戳)
18 int LOW[N];//u或u的子树能够追溯到的最早的栈中节点的序号(时间戳)
19 int n,m;//n:点的个数;m:边的条数
20 int cnt_edge;//边的计数器
21 int Index;//序号(时间戳)
22 int Bcnt; //有多少个强连通分量
23 stack<int> sk;
24
25 void add_edge(int u,int v)//邻接表存储
26 {
27     Edge[cnt_edge].next=node[u];
28     Edge[cnt_edge].v=v;
29     node[u]=cnt_edge++;
30 }
31
32 void tarjan(int u)
33 {
34     DFN[u]=LOW[u]=++Index;
35     instack[u]=1;
36     sk.push(u);
37     for(int i=node[u];i!=-1;i=Edge[i].next)
38     {
39         int v=Edge[i].v;
40         if(!DFN[v])//如果点v没被访问
41         {
42             tarjan(v);
43             LOW[u]=min(LOW[u],LOW[v]);
44         }
45         else //如果点v已经被访问过
46         {
47             if(instack[v]&&DFN[v]<LOW[u])
48                 LOW[u]=DFN[v];
49         }
50     }
51     if(DFN[u]==LOW[u])
52     {
53         Bcnt++;
54         int t;
55         do{
56             t=sk.top();
57             sk.pop();
58             instack[t]=0;
59             Belong[t]=Bcnt;
60         }while(t!=u);
61     }
62 }
63
64 int main()
65 {
66     freopen("sample.txt","r",stdin);
67     memset(node,-1,sizeof(node));
68     scanf("%d %d",&n,&m);
69     for(int i=1;i<=m;i++)
70     {
71         int a,b;
72         scanf("%d %d",&a,&b);
73         add_edge(a,b);
74     }
75     for(int i=1;i<=n;i++)
76     {
77         if(!DFN[i])
78         {
79             tarjan(i);
80         }
81     }
82     for(int i=1;i<=n;i++)
83     {
84         printf("%d ",Belong[i]);
85     }
86     return  0;
87 }


Network of Schools

http://poj.org/problem?id=1236

Description

A number of schools are connected to a computer network. Agreements have been developed among those schools: each school maintains a list of schools to which it distributes software (the “receiving schools”). Note that if B is in the distribution list of school A, then A does not necessarily appear in the list of school B 
You are to write a program that computes the minimal number of schools that must receive a copy of the new software in order for the software to reach all schools in the network according to the agreement (Subtask A). As a further task, we want to ensure that by sending the copy of new software to an arbitrary school, this software will reach all schools in the network. To achieve this goal we may have to extend the lists of receivers by new members. Compute the minimal number of extensions that have to be made so that whatever school we send the new software to, it will reach all other schools (Subtask B). One extension means introducing one new member into the list of receivers of one school.

Input

The first line contains an integer N: the number of schools in the network (2 <= N <= 100). The schools are identified by the first N positive integers. Each of the next N lines describes a list of receivers. The line i+1 contains the identifiers of the receivers of school i. Each list ends with a 0. An empty list contains a 0 alone in the line.

Output

Your program should write two lines to the standard output. The first line should contain one positive integer: the solution of subtask A. The second line should contain the solution of subtask B.

Sample Input

5
2 4 3 0
4 5 0
0
0
1 0

Sample Output

1
2

题目大意:给你一个有向图,求出最少选几个点,可以使整个图都是它们及它们的子节点。再求出最少加几条边可使原图变为强连通图。

题解:此题需要稍稍思考一下。求强连通分量并进行缩点后,设入度为0的点有a个,出度为零的点有b个。则ans1=a,ans2 = max(a,b)。另有特殊情况,当共有一个强连通分量时,ans2=0。

我们可以先进行缩点求出dag图,然后我们考虑第一个问题,求最少发几套软件可以全覆盖,首先题意已经保证了是联通的。然后我们可以想,如果我们把所有没有入边的点都放上软件,是一定可行的。有入边的一定会通过一些边最终从一定有出边的发放软件的地方获得软件。

然后我们考虑第二个问题。这是一个连通图。如果我们有些点没有入点,有些点没出点。那我们如果想办法将入点和一些出点相连,就能保证最后会成为很多圆相连。这样子答案就是没有入边的点和没有出边的点的最大值。

问题A:在网络中需要多少电脑才能使所有学校都有软件,通过强连通算法缩点,构建DAG图,图中一定有入度为0的节点,由于这种节点无法通过别的节点传送软件,所以必须放一台电脑,求出入度为0的节点个数即可。

问题B:在该网络中最少添加多少线路,使在任意节点放置电脑所有学校都可以有软件,答案是求入度为0的节点个数和出度为0的节点个数的最大值。

证明B:

1.以下所有点都是孤立的,连接两个点时,出发的边连接的是起始点中出度为0的点,终点中入度为0的点。

(1).定义当点数n=1时,如果是缩点构成的一个点必须添加一条反身边,否则不用添加边。

(2).当点数n=2时,连接两个点,缩成一个点,然后按规则(1),可得最少添加边数为2。

(3).当n>2时,通过规则(2)不断缩点,当点缩为一个时按规则(1),可得最少添加边数为n。

2.从最旁边的入度为0的节点出发与其下的最旁边的出度为0节点配对,将途中所有点包括起点和终点缩成一个点,并断开所有与其他节点连接的边,通过这样可以使DAG图形成由n(n>0)个孤立点组成的图,求最少添加边数是该图强连通,则可有1得出最少添加边数为n,由于n个孤立点都是入度为0的节点,出度为0的节点或两者配对形成的,可知n是入度为0的节点个数和出度为0的节点个数的最大值。

  1 #include <stdio.h>
  2 #include <iostream>
  3 #include <string.h>
  4 #include <algorithm>
  5 #include <stack>
  6 #include <string>
  7 #include <sstream>
  8 using namespace std;
  9 const int maxn=1e6+5; // 不能写成#define maxn 1e6+5,会编译错误
 10
 11 struct Edge{
 12     int v;
 13     int next;
 14 }Edge[maxn];//边的集合
 15
 16 int node[maxn];//顶点集合
 17 int instack[maxn];//标记是否在stack中
 18 int Belong[maxn];//各顶点属于哪个强连通分量
 19 int DFN[maxn];//节点u搜索的序号(时间戳)
 20 int LOW[maxn];//u或u的子树能够追溯到的最早的栈中节点的序号(时间戳)
 21 int n,m;//n:点的个数;m:边的条数
 22 int cnt_edge;//边的计数器
 23 int Index;//序号(时间戳)
 24 int Bcnt; //有多少个强连通分量
 25 int out[maxn];//存储出度
 26 int in[maxn];//存储入度
 27 stack<int> sk;
 28
 29 void add_edge(int u,int v)//邻接表存储
 30 {
 31     Edge[cnt_edge].next=node[u];
 32     Edge[cnt_edge].v=v;
 33     node[u]=cnt_edge++;
 34 }
 35
 36 void tarjan(int u)
 37 {
 38     DFN[u]=LOW[u]=++Index;
 39     instack[u]=1;
 40     sk.push(u);
 41     for(int i=node[u];i!=-1;i=Edge[i].next)
 42     {
 43         int v=Edge[i].v;
 44         if(!DFN[v])//如果点v没被访问
 45         {
 46             tarjan(v);
 47             LOW[u]=min(LOW[u],LOW[v]);
 48         }
 49         else //如果点v已经被访问过
 50         {
 51             if(instack[v]&&DFN[v]<LOW[u])
 52                 LOW[u]=DFN[v];
 53         }
 54     }
 55     if(DFN[u]==LOW[u])
 56     {
 57         Bcnt++;
 58         int t;
 59         do{
 60             t=sk.top();
 61             sk.pop();
 62             instack[t]=0;
 63             Belong[t]=Bcnt;
 64         }while(t!=u);
 65     }
 66 }
 67
 68 void work()
 69 {
 70     for(int i=1;i<=n;i++)
 71     {
 72         for(int j=node[i];j!=-1;j=Edge[j].next)
 73         {
 74             int v=Edge[j].v;
 75             if(Belong[i]!=Belong[v])
 76             {
 77                 out[Belong[i]]++;
 78                 in[Belong[v]]++;
 79             }
 80         }
 81     }
 82     int RU=0;
 83     int CHU=0;
 84     for(int i=1;i<=Bcnt;i++)
 85     {
 86         if(!in[i]) RU++;
 87         if(!out[i]) CHU++;
 88     }
 89     if(Bcnt==1)
 90         printf("1\n0\n");
 91     else
 92         printf("%d\n%d\n",RU,max(RU,CHU));
 93 }
 94
 95 int main()
 96 {
 97     freopen("sample.txt","r",stdin);
 98     memset(node,-1,sizeof(node));
 99     scanf("%d",&n);
100     getchar();
101     for(int i=1;i<=n;i++)
102     {
103         int v;
104         string str;
105         getline(cin,str);
106         istringstream ss(str);
107         while(ss >> v&&v)
108         {
109             add_edge(i,v);
110         }
111     }
112     for(int i=1;i<=n;i++)
113     {
114         if(!DFN[i])
115         {
116             tarjan(i);
117         }
118     }
119     work();
120     return  0;
121 }



图之强连通、强连通图、强连通分量 Tarjan算法

原文地址:https://www.cnblogs.com/jiamian/p/11187440.html

时间: 2024-10-12 20:07:29

图之强连通、强连通图、强连通分量 Tarjan算法的相关文章

HDU 1269 强连通分量tarjan算法

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

求图的强连通分量--tarjan算法

一:tarjan算法详解 ?思想: ? ?做一遍DFS,用dfn[i]表示编号为i的节点在DFS过程中的访问序号(也可以叫做开始时间)用low[i]表示i节点DFS过程中i的下方节点所能到达的开始时间最早的节点的开始时间.(也就是之后的深搜所能到达的最小开始时间)初始时dfn[i]=low[i] ? ?在DFS过程中会形成一搜索树.在搜索树上越先遍历到的节点,显然dfn的值就越小. ? ?DFS过程中,碰到哪个节点,就将哪个节点入栈.栈中节点只有在其所属的强连通分量已经全部求出时,才会出栈. ?

图论-强连通分量-Tarjan算法

有关概念: 如果图中两个结点可以相互通达,则称两个结点强连通. 如果有向图G的每两个结点都强连通,称G是一个强连通图. 有向图的极大强连通子图(没有被其他强连通子图包含),称为强连通分量.(这个定义在百科上和别的大神的博客中不太一样,暂且采用百科上的定义) Tarjan算法的功能就是求有向图中的强连通分量 思路: 定义DFNi存放访问到i结点的次序(时间戳),Lowi存放i结点及向i下方深搜到的结点中能追溯到的访问次序最小的结点的访问次序(即这些结点回溯上去能找到的最小的DFN值),找到未被访问

强连通分量--tarjan算法

今天学了一个强连通分量,用tarjan做.北京之前讲过,今天讲完和之前一样,没有什么进步.上课没听讲,只好回来搞,这里安利一个博客:链接 https://blog.csdn.net/qq_34374664/article/details/77488976 讲一下我自己的体会吧,其实就是维护一个栈,然后树上跑dfs,每个节点存两个值:dn和low,dn代表dfs的顺序(时间),low代表的是他可以连通的最小的节点. 模拟一下,然后就会发现,其实整个算法就是模拟了一下将每个点压入栈.然后遇到之前在栈

poj 2186 Popular Cows 【强连通分量Tarjan算法 + 树问题】

题目地址:http://poj.org/problem?id=2186 Popular Cows Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 27496   Accepted: 11059 Description Every cow's dream is to become the most popular cow in the herd. In a herd of N (1 <= N <= 10,000) cows

求强连通分量Tarjan算法

int dfn[16]; // 时间戳 int dfn_num = 0; // 时间 int low[16]; // 节点u所能访问到的最小时间戳 int inSt[16]; // 节点u是否在栈中. int st[16]; int top = 0; // 我们维护的信息. int col[16]; // 给节点染色, 同一个连通块的节点应该是同一个颜色的. int col_num = 0; // 颜色值. int size[16]; // 每个颜色值所拥有的块数. /* 第一步: 访问当前节点

强连通分量Tarjan算法模板

#include<map> #include<set> #include<cmath> #include<stack> #include<queue> #include<cstdio> #include<string> #include<vector> #include<cstring> #include<iomanip> #include<sstream> #include

强连通分量(tarjan求强连通分量)

双DFS方法就是正dfs扫一遍,然后将边反向dfs扫一遍.<挑战程序设计>上有说明. 双dfs代码: 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <vector> 5 6 using namespace std; 7 const int MAXN = 1e4 + 5; 8 vector <int> G[MAXN]; //图的邻接表

?数据结构-图之强连通

数据结构-图之强连通 在一个无向图G中,若从顶点v_i到顶点v_j有路径相连(当然从v_j到v_i也一定有路径),则称v_i和v_j是连通的.如果G是有向图,那么连接v_i和v_j的路径中所有的边都必须同向.如果图中任意两点都是连通的,那么图被称作连通图.图的连通性是图的基本性质. 连通分量:无向图G的一个极大连通子图称为G的一个连通分量(或连通分支).连通图只有一个连通分量,即其自身:非连通的无向图有多个连通分量. 初级通路:通路中所有的顶点互不相同.初级通路必为简单通路,但反之不真. 强连通