强连通------tarjan算法详解及与缩点联合运用

@强连通基本定义

什么是tarjan算法?(如何求强连通)

1.首先定义两个数组dfn和low,
dfn[x]表示x节点是第几个被遍历到的。
low[x]表示包含x在内的强连通分量的dfn的最小值。(也就是说这个强连通分量中最早被遍历到的)
2,我们用一个栈stack来存储遍历到的点,再定义一个数组vis[],把当前搜到的点的入栈标记为vis[x]=1。
3,对于每一个当前节点的子节点,如果之前没有遍历到,就往下遍历,并在遍历后取low的min值:low[x]=min(low[x],low[v])。如果之前已经遍历到,且是栈中元素(vis[v]=1),那么我们就不再往下继续搜了,直接传递dfn的值:low[x]=min(low[x],dfn[v]);
4,如果当前节点的dfn的值等于low的值,由于low表示包含当前节点的强连通分量的最小dfn的值,那么我们就可以认为low被传递了一圈又回到了当前dfn,也就代表找到了一个强联通分量,那么我们就把从当前的栈顶一直到当前元素的栈中元素全部弹出并作为强联通分量的一部分。

void tarjan(int X)
{
	vis[X]=1;
	dfn[X]=low[X]=++cnt;
	sta.push(X);
	for(int i=0;i<vec[X].size();i++)
	{
		int to=vec[X][i];
		if(!dfn[to])
		{
			tarjan(to);
			low[X]=min(low[X],low[to]);
		}
		else if(vis[to])
		low[X]=min(low[X],dfn[to]);
	}
	if(low[X]==dfn[X])
	{
		flag++;//计算有多少个强连通分量
		while(sta.top()!=X)
		{
			vis[sta.top()]=0;
			sta.pop();
		}
		vis[sta.top()]=0;
			sta.pop();//把x本身出栈
	}
}

@迷宫城堡(HDU-1269)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+2;
int n,m,dfn[maxn],low[maxn],flag,vis[maxn],cnt;
vector<int> vec[maxn];
stack<int> sta;
void tarjan(int X)
{
	vis[X]=1;
	dfn[X]=low[X]=++cnt;
	sta.push(X);
	for(int i=0;i<vec[X].size();i++)
	{
		int to=vec[X][i];
		if(!dfn[to])
		{
			tarjan(to);
			low[X]=min(low[X],low[to]);
		}
		else if(vis[to])
		low[X]=min(low[X],dfn[to]);
	}
	if(low[X]==dfn[X])
	{
		flag++;
		while(sta.top()!=X)
		{
			vis[sta.top()]=0;
			sta.pop();
		}
		vis[sta.top()]=0;
			sta.pop();
	}
}
int main()
{
	int n,m;
	while(cin>>n>>m) {
	if(n==m&&n==0)
	    break;
	for(int i=1;i<=n;i++)
	  vec[i].clear();
	for(int i=0;i<m;i++)
	{
		int x,y;
		cin>>x>>y;
		vec[x].push_back(y);
		}
	flag=0;
	cnt=0;//cout<<"1";
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++)
	{
		if(!dfn[i])
		tarjan(i);
	}
	if(flag==1)
	cout<<"Yes"<<endl;
	else
	cout<<"No"<<endl;}
	return 0;
}
 

与缩点结合运用

最小点基算法(缩点):

是强连通的一个应用,找出图G的所有强连通分量,将每个强连通分量各自缩成一个点v,即删除各个强连通分量内的边,连接v与原强连通分量相连接的点。缩点后再找出入度为0的点,就是缩点前的最高的强连通分量。最小点基就从最高的强连通分量中任选一个顶点,组成的顶点集B就是图G的一个最小点基。

最小权点基(缩权):

最小权点基的算法基本上根最小点基一样,最小点基最后是从最高的强连通分量中任选一个顶点,组成最小点基;而最小权点基则是从最高的强连通分量中取权值最小的顶点,组成最小权点基。

例题
@Summer Holiday(HDU-1827)

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#include<stack>
using namespace std;
const int maxn=1e3+3;
int N,M,summ,flag,n_val[maxn],low[maxn],dfn[maxn],vis[maxn],cnt,tar[maxn],ru[maxn],mincost[maxn];
vector<int> mapp[maxn];
stack<int> sta;
void tarjan(int x)
{
	vis[x]=1;
	dfn[x]=low[x]=++cnt;
	sta.push(x);
	for(int i=0;i<mapp[x].size();i++)
	{
		int to=mapp[x][i];
		if(!dfn[to])
		{
			tarjan(to);
			low[x]=min(low[x],low[to]);
		}
		else if(vis[to])
		low[x]=min(low[x],dfn[to]);
	 } 
	 if(dfn[x]==low[x])
	 {
	 	flag++;
	 	while(sta.top()!=x)
	 	{
	 		vis[sta.top()]=0;
	 		tar[sta.top()]=flag;//tar数组就是中x表示强连通中的点,tar【x】表示这个强连通是第几个(给强连通命名排序了),把每个强连通都变成一个点
	 		sta.pop();
		 }
		 vis[sta.top()]=0;
		 tar[sta.top()]=flag;
		 sta.pop();
	 }
}
int main()
{
	while(~scanf("%d%d",&N,&M))
	{
	for(int i=0;i<=N;i++)
	mapp[i].clear();
	memset(tar,0,sizeof(tar));
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(vis,0,sizeof(vis));
	memset(ru,0,sizeof(ru));
	for(int i=1;i<=N;i++)
	{
		scanf("%d",&n_val[i]);
	}
	while(M--)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		mapp[x].push_back(y);
	}
	cnt=0;
	summ=0;
	flag=0;
	for(int i=1;i<=N;i++)
	if(!dfn[i])
	tarjan(i);
	for(int i=1;i<=N;i++)
	{
		for(int j=0;j<mapp[i].size();j++)
		{
			int to=mapp[i][j];
			if(tar[i]!=tar[to])//如果两个不是一个强连通分量,但i可以到to
			 ru[tar[to]]=1;//标记这个点可通到(如果ru【x】为零,代表x为最高的强连通分量)
		}
	}
	for(int i=1;i<=N;i++)
	mincost[i]=1000000000;
	for(int i=1;i<=N;i++)
	{
		if(mincost[tar[i]]>n_val[i]&&ru[tar[i]]==0)//权值比较,找出最高的强连通分量的最小权值
		mincost[tar[i]]=n_val[i];
	}
	int num=0;
	for(int i=1;i<=flag;i++)
	{
		if(ru[i]==0)//从强连通分量中找最高的强连通分量
		{
			num++;
			summ+=mincost[i];
		}
	}
	printf("%d %d\n",num,summ);}
	return 0;
 } 
发布了44 篇原创文章 · 获赞 4 · 访问量 1065

猜你喜欢

转载自blog.csdn.net/qq_44162236/article/details/104070142
今日推荐