強連通------tarjan演算法詳解及與縮點聯合運用

青雲66發表於2020-01-22

@強連通基本定義

什麼是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;
 } 

相關文章