最近公共祖先(LCA)問題-在線ST算法

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);
    }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章