詳解使用 Tarjan 求 LCA 問題(圖解)

LCA問題有多種求法,例如倍增,Tarjan。node

本篇博文講解如何使用Tarjan求LCA。c++

若是你還不知道什麼是LCA,不要緊,本文會詳細解釋。算法

在本文中,由於我懶爲方便理解,使用二叉樹進行示範。
數組

LCA是什麼,能吃嗎?

LCA是樹上最近公共祖先問題。函數

最近公共祖先就是樹上有兩個結點,找一個結點,是他們的公共祖先,而且離他們兩個結點最近。優化

例如這是一棵樹:ui

 

樹上 4,7 兩個結點的 LCA 就是 2 了。spa

1 雖然也是他們的公共祖先,但並非最近的。3d

再舉個例子,8,5 的祖先是 5。8,6 的祖先是 1。blog

怎麼求LCA問題?

在開頭已經說過了,LCA 問題有多種求法。本文要介紹的是相對簡單的 Tarjan 求 LCA。

注意:Tarjan 求 LCA 是一種離線的算法,也就是說它一遍求出全部須要求的點的 LCA,而不是須要求哪兩個點再去求。

在開始介紹前的補充

Tarjan 求 LCA 須要用到並查集,如下是本人使用的並查集模板。

int fa[100000];
void reset(){
	for (int i=1;i<=100000;i++){
		fa[i]=i;
	}
}
int getfa(int x){
	return fa[x]==x?x:getfa(fa[x]);
}
void marge(int x,int y){
	fa[getfa(y)]=getfa(x);
}

因爲 Tarjan 是在遍歷到目標點的時候得出答案並輸出,那麼若是你不輸出,就須要使用一些東西來記錄它(通常不用)。

關於記錄

除非你以後須要 LCA 的結果再作一些操做,不然不須要記錄,直接在 DFS 中輸出便可。

我使用的是 STL 中的 Map 和 Pair,由於 LCA 是求兩個點,Pair 正好能夠知足一對數據。而 Map 的哈希機制能夠實現 O(1) 查找。

Tarjan 求 LCA 作法

整體思想

遍歷每個結點並使用並查集記錄父子關係。

Tarjan 是一種 DFS 的思想。咱們須要從根結點去遍歷這棵樹。

當遍歷到某一個結點(稱之爲 x) 時,你有如下幾點須要作的。

1將當前結點標記爲已經訪問。

2遞歸遍歷全部它的子節點(稱之爲 y),並在遞歸執行完後用並查集合並 x 和 y。

3遍歷與當前節點有查詢關係的結點(稱之爲 z)(便是須要查詢 LCA 的另外一些結點),若是 z 已經訪問,那麼 x 與 z 的 LCA 就是 $getfa(z)$(這個是並查集中的查找函數),輸出或者記錄下來就能夠了。

這是僞代碼

void tarjan(int x){
    //在本代碼段中,s[i]爲第i個子節點 , t[i]爲第i個和當前節點有查詢關係的結點。
    vis[x]=1;//標記已經訪問,vis是記錄是否已訪問的數組
    for (i=1;i<=子節點數;i++){//枚舉子節點 (遞歸併合併)
        tarjan(s[i]);
        marge(x,s[i]);//並查集合並
    }
    for (i=1;i<=有查詢關係的結點數;i++){
        if (vis[t[i]]){
            cout<<x<<"和"<<t[i]<<"的LCA是"<<getfa(t[i])<<endl;//若是t[i]已經訪問了輸出(getfa是並查集查找函數)
        }
    }
}

核心代碼就這麼一點?對,就這麼一點。

若是你還不理解,那麼能夠跳轉到最後一章看圖解演示。

一些重要的細節

爲了接下來的講解,下面咱們明確一下讀入方式,不一樣的讀入方式能夠本身變通一下。

第一行兩個數 n 和 q,表示結點數和查詢數。

接下來 n 行每行兩個數,表示左子結點和右子結點編號,如沒有則是 -1。

接下來 q 行每行兩個數,表示查詢的兩個結點編號。

例如上圖的樹,讀入爲

9 5
2 3
4 5
-1 6
-1 -1
7 8 
-1 9
-1 -1
-1 -1
-1 -1
5 4
7 4
7 8
9 3
8 6

如何存儲查詢關係

我在這裏用的方法是二維數組。

int t[100000][10],top[100000];
//t[i][j]表示編號爲i的結點,第j個和它有查詢關係的點的編號
//top[i]表示編號爲i的結點與它有查詢關係的點的數量

  

注意:須要雙向存儲關係。例如結點 2 和 3,不只要更新t[2],還要更新t[3]。

讀入代碼長這樣:

for (int i=1;i<=q;i++){
	cin>>a[i]>>b[i];
	t[a[i]][++top[a[i]]]=b[i];
	t[b[i]][++top[b[i]]]=a[i];
}

固然若是你想要優化下空間那麼把這個數組變成vector也是沒問題的。

這就沒了...

代碼

直接輸出的寫法

#include<bits/stdc++.h>
using namespace std;
int n,k,q,v[100000];
map<pair<int,int>,int> ans;//存答案 
int t[100000][10],top[100000];//存儲查詢關係
struct node{
	int l,r;
};
node s[100000];
/*並查集*/
int fa[100000];
void reset(){
	for (int i=1;i<=n;i++){
		fa[i]=i;
	}
}
int getfa(int x){
	return fa[x]==x?x:getfa(fa[x]);
}
void marge(int x,int y){
	fa[getfa(y)]=getfa(x);
}
/*------*/
void tarjan(int x){
	v[x]=1;//標記已訪問
	node p=s[x];//獲取當前結點結構體
	if (p.l!=-1){
		tarjan(p.l);
		marge(x,p.l);
	}
	if (p.r!=-1){
		tarjan(p.r);
		marge(x,p.r);
	}//分別對l和r結點進行操做
	for (int i=1;i<=top[x];i++){
		if (v[t[x][i]]){
			cout<<getfa(t[x][i])<<endl;
		}//輸出
	}
}
int main(){
	cin>>n>>q;
	for (int i=1;i<=n;i++){
		cin>>s[i].l>>s[i].r;
	}
	for (int i=1;i<=q;i++){
		int a,b;
		cin>>a>>b;
        	t[a][++top[a]]=b;//存儲查詢關係
        	t[b][++top[b]]=a;
	}
	reset();//初始化並查集
	tarjan(1);//tarjan 求 LCA
}

先記錄而不輸出的寫法

#include<bits/stdc++.h>
using namespace std;
int n,k,q,v[100000];
map<pair<int,int>,int> ans;//存答案 
int t[100000][10],top[100000];//存儲查詢關係
int a[100000],b[100000];
struct node{
	int l,r;
};
node s[100000];
/*並查集*/
int fa[100000];
void reset(){
	for (int i=1;i<=n;i++){
		fa[i]=i;
	}
}
int getfa(int x){
	return fa[x]==x?x:getfa(fa[x]);
}
void marge(int x,int y){
	fa[getfa(y)]=getfa(x);
}
/*------*/
void tarjan(int x){
	v[x]=1;
	node p=s[x];
	if (p.l!=-1){
		tarjan(p.l);
		marge(x,p.l);
	}
	if (p.r!=-1){
		tarjan(p.r);
		marge(x,p.r);
	}
	for (int i=1;i<=top[x];i++){
		if (v[t[x][i]]){
			pair<int,int> tmp,tmp1;//用pair配合map來存儲答案 
			tmp=make_pair(x,t[x][i]);
			tmp1=make_pair(t[x][i],x);//兩個pair的目的是例如3 2這種數據若是搜到3纔有答案那麼進時的順序不止是(3,2),還有(2,3),方便輸出結果時查詢 
			ans[tmp]=getfa(t[x][i]);
			ans[tmp1]=getfa(t[x][i]);
			cout<<"#"<<ans[tmp]<<endl;
		}
	}
}
int main(){
	cin>>n>>q;
	for (int i=1;i<=n;i++){
		cin>>s[i].l>>s[i].r;
	}
	for (int i=1;i<=q;i++){
		cin>>a[i]>>b[i];
		t[a[i]][++top[a[i]]]=b[i];
		t[b[i]][++top[b[i]]]=a[i];
	}
	reset();
	tarjan(1);
	for (int i=1;i<=q;i++){
		pair<int,int> tmp;
		tmp=make_pair(b[i],a[i]);
		cout<<a[i]<<"-"<<b[i]<<":"<<ans[tmp]<<endl;
	}
}

算法演示

 

相關文章
相關標籤/搜索