LCA算法詳解
1. 概述
LCA(Least Common Ancestors),即最近公共祖先,是指這樣一個問題:在有根樹中,找出某兩個結點u和v最近的公共祖先(另一種說法,離樹根最遠的公共祖先)。對於該問題,最容易想到的解決方案是遍歷,複雜度是O(n)。但當數據量非常大且查詢很頻繁時,該算法也許會存在問題。
2. 在線ST算法
解決此問題存在兩種經典的算法,一種是在線ST算法,另外一種是離線的Tarjan算法,
所謂在線算法是指用戶每輸入一個查詢便馬上處理一個查詢,該算法一般用較長的時間做預處理,待信息充足以後便可以用較少的時間回答每個查詢。
所謂離線算法,是指首先讀入所有的詢問(求一次LCA叫做一次詢問),然後重新組織查詢處理順序以便得到更高效的處理方法
在線算法DFS+ST描述:將樹看成一個無向圖,u和v的公共祖先一定在u與v之間的最短路徑上
● DFS:從樹的根節點T開始,深度遍歷樹,並記錄下每次到達的頂點。第一個的結點是root(T),每經過一條邊都記錄它的端點。由於每條邊恰好經過2次,因此一共記錄了2n-1個結點,用E[1, … , 2n-1]來表示。
● 計算R[]:用R[i]表示E數組中值爲i的元素第一次出現的下標,即如果R[u] < R[v]時,DFS訪問的順序是E[R[u], R[u]+1, …, R[v]]。雖然其中包含u的後代,但深度最小的還是u與v的公共祖先。
● RMQ:當R[u] ≥ R[v]時,LCA[T, u, v] = RMQ(L, R[v], R[u]);否則LCA[T, u, v] = RMQ(L, R[u], R[v]),計算RMQ。
在線算法DFS+ST代碼如下:
1.數據結構描述:
// 頭結點信息
private Node heads[];
// 邊信息
private Edge edges[];
// 深度遍歷過程中每個節點第一次出現的序號
private int first[];
// 每個節點出現的深度
private int depth[];
// 深度遍歷序列
private int travel[];
// 保存節點到根節點的距離
private int dir[];
// 訪問記錄矩陣
private boolean vis[];
private RMQ mRmq;
/**
* 鄰接表頭結點信息
*/
class Node {
private int sno;// 節點編號
private Edge firstEdge;
}
/**
* 鄰接表邊信息
*/
class Edge {
private int sno;
private int from;
private int to;
private int wight;
private Edge next;
}
1.算法步驟描述:
##### 1.根據輸入的節點和權重信息建立鄰接表
/**
* 無向圖創建路徑
*
* @param from
* @param to
* @param wight
*/
public void createEdge(int from, int to, int wight) {
addEdge(from, to, wight);
addEdge(to, from, wight);
}
/**
* 頭插法創建鄰接表
*
* @param from
* @param to
* @param wight
*/
private void addEdge(int from, int to, int wight) {
Edge edge = new Edge();
edge.from = from;
edge.to = to;
edge.wight = wight;
edge.sno = edgeNum;
edges[edgeNum++] = edge;
edge.next = heads[from].firstEdge;
heads[from].sno = from;
heads[from].firstEdge = edge;
}
##### 2.深度優先遍歷,計算訪問序列、深度信息、節點第一次出現的位置信息等
/**
* 深度遍歷鄰接表
*
* @param u
* @param dep
*/
public void travelInDepth(int u, int dep) {
vis[u] = true;
travel[++index] = u;
first[u] = index;
depth[index] = dep;
for (Edge edge = heads[u].firstEdge; edge != null; edge = edge.next) {
if (!vis[edge.to]) {
int v = edge.to;
dir[v] = dir[u] + edge.wight;
travelInDepth(v, dep + 1);
travel[++index] = u;
depth[index] = dep;
}
}
}
##### 3.根據輸入的查詢,回答結果
/**
* 回答節點的最近公共祖先節點
* @param u
* @param v
* @return
*/
public int getLCANode(int u, int v) {
if (mRmq == null) {
mRmq = new RMQ();
mRmq.RMQInit(depth);
}
u = first[u];
v = first[v];
if (u < v) {
return mRmq.getMax(u, v);
}
return mRmq.getMax(v, u);
}