Tarjan演算法求強連通分量總結

mrh929發表於2016-07-18


     Tarjan演算法求強連通分量總結

首先明確強連通分量的概念:如果圖中的任意兩個點都能互相到達,則為強連通分量。極大強連通分量:不被其它任何強連通分量包含的強連通分量。

強連通分量主要與兩種邊有關:交叉邊和後向邊。

交叉邊是兩個無關係的點之間所構成的,而後向邊是根節點的子節點指向根結點的一種邊

Tarjan演算法原理:

  1. 建立low陣列(用於記錄該點所在的連通子圖的根節點的搜尋子樹所遍歷的時間)與dfn陣列(記錄當前點遍歷的時間)Index:記錄搜尋過程所進行的時間

  2. 初始化dfnindex,當前點u的初始值為時間

  3. u入棧並標記

  4. 遍歷與u相連的所有點(記為v

  1. 如果v未被遍歷,遞迴遍歷v點,然後更新low[u]     low[u]=min(low[u],low[v]);

  2. 如果 v已經被遍歷且在棧中直接更新low[u]

    low[u]=min(low[u],dfn[v]);

  1. 判斷low[u]==low[v]如果成立,將包括u在內的所有點出棧並記錄,則求得強連通分量之一

  2. 繼續遍歷,直至結束

  3. 注意:在一個圖中,起點不唯一,所以tarjan演算法應該遍歷所有未被遍歷的點(vis[i]==0)

 

本演算法主要應用範圍是對複雜的有向圖的縮點優化。如果某些點構成一個環,完全可以把它們看作一個點,加快程式執行效率

 

Question:如果是有向有權圖使用縮點時應該怎麼處理權值的問題?

 

 

FZOJ1638求強連通分量

描述

輸入一個圖,輸出該圖中的最大強連通分量。

輸入

第一行:nmn<=10000,m<=100000,n為節點個數,m為邊的條數)

接下來m行,每行兩個數:ab,表示a指向b的邊(a,b為非負整數);

輸出

輸出最大強連通分量的節點,按照節點編號從小到大輸出,如果有多個強連通分量節點數相同,則輸出節點編號字典序較小的。

樣例輸入

6 8

1 3

3 5

5 6

1 2

4 1

2 4

4 6

3 4

樣例輸出

1 2 3 4


 

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int first[10020],nxt[100200],go[100200],arcnum=1;
int dfn[10020],low[10020],stack[100200],top,dex,vis[10020];
int rt,ans[10020],temp[10020],sum;
void addarc(int a,int b){
    nxt[arcnum]=first[a];
    first[a]=arcnum;
    go[arcnum++]=b;
}
void tarjan(int u){
    dfn[u]=low[u]=++dex;
    stack[++top]=u; vis[u]=1;
    for(int p=first[u];p!=0;p=nxt[p]){
        int v=go[p];
        if(vis[v]==0){
            vis[v]=1;
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v]==1)
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        int v; sum=0;
        do{
            v=stack[top--];
            vis[v]=2;
            temp[++sum]=v;
        }while(u!=v);
        if(rt<sum){
        	rt=sum;
        	for(int i=1;i<=sum;i++)
        		ans[i]=temp[i];
        }
    }
}
 
int main()
{
//	freopen("in.txt","r",stdin);
    int n,m,a,b;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&a,&b);
        addarc(a,b);
    }
     
    
    for(int i=0;i<=n-1;i++)
    	if(vis[i]==0){
    		top=0;dex=0;
    		memset(dfn,0,sizeof(dfn));
    		memset(low,0,sizeof(low));
    		stack[++top]=i;
    		tarjan(i);
    	}
    		
    sort(ans+1,ans+1+rt);
    for(int i=1;i<=rt;i++)
 	    printf("%d ",ans[i]);
     
    return 0;
}

 

Poj2186Popular Cows

Description

Every cow's dream is to become the mostpopular cow in the herd. In a herd of N (1 <= N <= 10,000) cows, you aregiven up to M (1 <= M <= 50,000) ordered pairs of the form (A, B) thattell you that cow A thinks that cow B is popular. Since popularity is transitive,if A thinks B is popular and B thinks C is popular, then A will also think thatC is

popular, even if this is not explicitlyspecified by an ordered pair in the input. Your task is to compute the numberof cows that are considered popular by every other cow.

 

Input

* Line 1: Two space-separated integers, Nand M

* Lines 2..1+M: Two space-separated numbersA and B, meaning that A thinks B is popular.

Output

output

* Line 1: A single integer that is thenumber of cows who are considered popular by every other cow.

 

Sample Input

3 3

1 2

2 1

2 3

 

Sample Output

1

 

Hint

Cow 3 is the only cow of high popularity.

 

題目大意

N頭奶牛(N≤10000)

M對關係(a , b),表示a認為b是受歡迎的

關係具有傳遞性,即若(a,b),(b,c)→(a,c)

詢問有多少頭奶牛是被其他所有奶牛認為是受歡迎的

 

這道題據說不使用強連通演算法優化也能做出來,就是說tarjan演算法只是進行優化。

進過分析可以發現,在有向無環圖DAG圖中,要想一頭(群)牛被其它所有牛歡迎,必須滿足只有它自己的出度為0,其它的牛所構成的圈子(環)的出度必須大於零才可以

這樣就得到了解決方法,用tarjan演算法算出所有的強連通分量並將它們合併成一個點,再做一次搜尋,如果這個環對外的出度為0則其中所有牛對其他牛的出度都為0

如果一個DAG圖有兩個以上出度為0得點,那麼總有一個點無法到達其它任意一個點

遍歷所有環與點,如果出度為0的只有一個則輸出答案,否則答案為0

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=10000+200,MAXM=50000+200;
int first[MAXN],nxt[MAXM],go[MAXM],arcnum=1;
int dfn[MAXN],low[MAXN],stack[MAXM],top;
int scc[MAXN],idx,cscc,vis[MAXN];//記錄強連通分量 
int cd[MAXN],cd_scc[MAXN];
void addarc(int a,int b){
	nxt[arcnum]=first[a];
	first[a]=arcnum;
	go[arcnum++]=b;
	cd[a]++;
}

void tarjan(int u){
	low[u]=dfn[u]=++idx;
	stack[++top]=u; vis[u]=1;
	for(int p=first[u];p!=0;p=nxt[p]){
		int v=go[p];
		if(vis[v]==0){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}else if(vis[v]==1)
			low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		int v;
		cscc++;
		do{
			v=stack[top--];
			vis[v]=2;
			scc[v]=cscc; 
		}while(u!=v);
	}
}

int main()
{
//	freopen("in.txt","r",stdin);
	int n,m,a,b;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&a,&b);
		addarc(a,b);
	}
	for(int i=1;i<=n;i++)
		if(vis[i]==0)
			tarjan(i);
			
	if(idx<n){
		printf("0");
		return 0;
	}
	for(int u=1;u<=n;u++)
		for(int p=first[u];p!=0;p=nxt[p]){
			int v=go[p];
			if(scc[u]!=scc[v])
				cd_scc[scc[u]]++;
		}
	int c1=0;
	for(int i=1;i<=cscc;i++){
		if(cd_scc[i]==0&&c1==0)
			c1=i;
		else if(c1!=0&&cd_scc[i]==0){
			printf("0");
			return 0;
		}
	}
	
	int ans=0;
	for(int i=1;i<=n;i++)
		if(scc[i]==c1)
			ans++;
	printf("%d",ans);
	
	return 0;
}

相關文章