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


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

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

如果你還不知道什么是LCA,沒關系,本文會詳細解釋。

在本文中,因為我懶為方便理解,使用二叉樹進行示范。

LCA是什么,能吃嗎?

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

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

例如這是一棵樹:

 

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

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

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

怎么求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;
	}
}

算法演示

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM