【tarjan】知识点讲解+例题

讲解内容 via https://blog.csdn.net/jeryjeryjery/article/details/52829142?locationNum=4&fps=1 ,写得不错,让懵逼了半天的我看懂了。

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

       Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。Tarjan算法有点类似于基于后序的深度遍历搜索和并查集的组合,充分利用回溯来解决问题。
在Tarjan算法中为每个节点i维护了以下几个变量:
DFN[i]:深度优先搜索遍历时节点i被搜索的次序。
low[i]:节点i能够回溯到的最早位于栈中的节点。
flag[i]:标记几点i是否在栈中。

Tarjan算法的运行过程:
(1).首先就是按照深度优先搜索算法搜索的次序对图中所有的节点进行搜索。
(2).在搜索过程中,对于任意节点u和与其相连的节点v,根据节点v是否在栈中来进行不同的操作:
*节点v不在栈中,即节点v还没有被访问过,则继续对v进行深度搜索。
*节点v已经在栈中,即已经被访问过,则判断节点v的DFN值和节点u的low值的大小来更新节点u的low值。如果节点v的 DFN值要小于节点u的low值,根据low值的定义(能够回溯到的最早的已经在栈中的节点),我们需要用DFN值来更新u 的low值
(3).在回溯过程中,对于任意节点u用其子节点v(其实不能算是子节点,只是在深度遍历的过程中,v是在u之后紧挨着u的节点)的   low值来更新节点u的low值。因为节点v能够回溯到的已经在栈中的节点,节点u也一定能够回溯到。因为存在从u到v的直接路径,所以v能够到的节点u也一定能够到。

(4).对于一个连通图,我们很容易想到,在该连通图中有且仅有一个节点u的DFN值和low值相等。该节点一定是在深度遍历的过程中,该连通图中第一个被访问过的节点,因为它的DFN值和low值最小,不会被该连通图中的其他节点所影响。

      下面我们证明为什么仅有一个节点的DFN和low值相等。假设有两个节点的DFN值和low值相等,由于这两个节点的DFN值一定不相同 (DFN值的定义就是深度遍历时被访问的先后次序),所以两个的low值也绝对不相等。由于位于同一个连通图中,所以两个节点必定相互可达,那么两者的low值一定会被另外一个所影响(要看谁的low值更小),所以不可能存在两对DFN值和low值相等的节点。

       所以我们在回溯的过程中就能够通过判断节点的low值和DFN值是否相等来判断是否已经找到一个子连通图。由于该连通图中的DFN值和low值相等的节点是该连通图中第一个被访问到的节点,又根据栈的特性(先压入  栈的节点在栈的更里面),则该节点在最里面。所以能够通过不停的弹栈,直到弹出该DFN值和low值相同的节点来弹出该连通图中所有的节点。


接下来是我的showtime:


用我的话来阐述算法思想:

哎呀....让我组织下语言....

首先要知道tarjan算法要用到的东西:dfn数组(记录每个点入栈的先后顺序,作为序号),low数组(表示这个点所能绕回去到达的点的最小序号),栈(首次被访问就该进栈,如果判断自己是强连通图了会出栈的),vis数组(判断你在栈中与否)。

算法思想其实就是比如说一个点嘛,你首次被访问首先把自己的dfn和low值赋好,进栈,vis值赋成1,然后去看你所指向的点:如果这个点还从来没被访问过,那就应该让这个点递归tarjan算法,从而你就可以和它比较,如果它的low值小那你就用它的low值(因为它能到达的最小点你也能到达!);如果这个点这个点被访问过了而且还在栈中(还在栈中的意思就是它并没有已经作为其他的连通图出栈了(如上图的点6)),那么就看看它的dfn值是不是比你的low值小,如果是,那么说明它是你目前可以到达的最小点,赶紧更新你的low值啊!(如图中的点3和点1关系)。

如果你所指向的点都按如上操作弄完了 即循环结束了,那么你就该比较你的dfn值==你的low值。因为如果你能处在一个强联通分量里面,且你的dfn值是你所能到达的点中最小的dfn值,那么你能到达的最小点难道不该是你自己吗?所以如果成立,说明假设成立,你处在一个强联通分量里。

那么根据栈“先进后出”,你现在肯定在栈底下吧,那就出栈,直到把你出了,那么你处于的这个强联通分量也就已经都出栈了。注意出栈的时候vis设为0,别去影响其他连通图的点。


例题(代码即tarjan算法模板)


思路

就是求最小连通分量的点个数!(当然,一个点自成连通分量的不算)

AC代码

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
vector<int> v[maxn];
int dfn[maxn];
int low[maxn];
int cnt=0;
int vis[maxn]; //1表示在栈中
stack<int> s;
int minn=0x3f3f3f3f;

void tarjan(int a)
{
	s.push(a);
	vis[a]=1;
	dfn[a]=low[a]=++cnt;
	for(int i=0;i<v[a].size();i++)
	{
		int e=v[a][i];
		if(dfn[e]==0) //该点还没被访问过
		{
			tarjan(e);
			low[a]=min(low[a],low[e]);
		} 
		else if(vis[e]==1) //被访问过且还在栈中
		{
			low[a]=min(low[a],dfn[e]);
		} 
	}
	if(dfn[a]==low[a]) //你属于一个强连通分量里 
	{
		int ans=0; //记录你的这个强连通分量里的点数
		while(1)
		{
			int t=s.top();
			s.pop();
			vis[t]=0;
			ans++;
			if(t==a)
				break;
		} 
		if(ans!=1)  //因为你自己一个点的强连通不符合题意 
			minn=min(minn,ans);
	}
}
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int e;
		scanf("%d",&e);
		v[i].push_back(e);
	}
	tarjan(1);
	cout<<minn;
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/m0_38033475/article/details/80069441