1. 程式人生 > >關於LCA(Tarjan+ST)

關於LCA(Tarjan+ST)

         又是深夜更新部落格睡覺。。。為的是寫一下今天學的LCA(Least Common Ancestors),也就是最近祖先問題。。。最近祖先也就是離根結點最遠的祖先。。。

有兩種演算法,一種是離線演算法:Tarjan演算法(突然覺得這個人提出了好多演算法。。。),第二種是線上演算法:ST演算法 。

          由於今天只看了Tarjan演算法、它的一些拓展和RMQ,並沒有寫ST的模板,再加上沒有資料結構的基礎。。到現在還不能透徹地瞭解樹、二叉樹和各種什麼什麼的二叉樹哭

這裡先貼一下大神的連結,http://dongxicheng.org/structure/lca-rmq/(後面的演算法介紹也就用大神的了/。。)接下去先寫 關於Tarjan演算法。。(其實我覺得Tarjan演算法更好一

點。。。)

Tarjan

所謂離線演算法,是指首先讀入所有的詢問(求一次LCA叫做一次詢問),然後重新組織查詢處理順序以便得到更高效的處理方法。Tarjan演算法是一個常見的用於解決LCA問題的離線演算法,它結合了深度優先遍歷和並查集,整個演算法為線性處理時間。

Tarjan演算法是基於並查集的,利用並查集優越的時空複雜度,可以實現LCA問題的O(n+Q)演算法,這裡Q表示詢問 的次數。更多關於並查集的資料,可閱讀這篇文章:《資料結構之並查集》。

同上一個演算法一樣,Tarjan演算法也要用到深度優先搜尋,演算法大體流程如下:對於新搜尋到的一個結點,首先建立由這個結點構成的集合,再對當前結點的每一個子樹進行搜尋,每搜尋完一棵子樹,則可確定子樹內的LCA詢問都已解決。其他的LCA詢問的結果必然在這個子樹之外,這時把子樹所形成的集合與當前結點的集合合併,並將當前結點設為這個集合的祖先。之後繼續搜尋下一棵子樹,直到當前結點的所有子樹搜尋完。這時把當前結點也設為已被檢查過的,同時可以處理有關當前結點的LCA詢問,如果有一個從當前結點到結點v的詢問,且v已被檢查過,則由於進行的是深度優先搜尋,當前結點與v的最近公共祖先一定還沒有被檢查,而這個最近公共祖先的包涵v的子樹一定已經搜尋過了,那麼這個最近公共祖先一定是v所在集合的祖先。

【舉例說明】

根據實現演算法可以看出,只有當某一棵子樹全部遍歷處理完成後,才將該子樹的根節點標記為黑色(初始化是白色),假設程式按上面的樹形結構進行遍歷,首先從節點1開始,然後遞迴處理根為2的子樹,當子樹2處理完畢後,節點2, 5, 6均為黑色;接著要回溯處理3子樹,首先被染黑的是節點7(因為節點7作為葉子不用深搜,直接處理),接著節點7就會檢視所有詢問(7, x)的節點對,假如存在(7, 5),因為節點5已經被染黑,所以就可以斷定(7, 5)的最近公共祖先就是find(5).ancestor,即節點1(因為2子樹處理完畢後,子樹2和節點1進行了union,find(5)返回了合併後的樹的根1,此時樹根的ancestor的值就是1)。有人會問如果沒有(7, 5),而是有(5, 7)詢問對怎麼處理呢? 我們可以在程式初始化的時候做個技巧,將詢問對(a, b)和(b, a)全部儲存,這樣就能保證完整性。

 我覺得上面的文字寫的真是太讚了!偷笑下面的模板程式碼是我自己寫的

/***************************************************************************************************
查詢過程始終是框定在某一棵子樹裡面的,所以只有兩種情況:1.另外一點沒有被遍歷過,

那麼等待下次回溯的時候回溯到要查詢的另外一點2.另外一點已經被遍歷過,那麼就輸出

另外一點的祖先節點,而這個祖先節點必定為他們兩者的最近祖先節點(原因很簡單,這

是dfs,對於這個祖先節點,必然先遍歷完它的左子樹再遍歷它的右子樹,此時他的左子樹

的所有點都會存有這個祖先節點
******************************************************************************************************/
#include <cstdio>
#include <string.h>
#include <vector>
using namespace std;
int const MAX = 10000;
vector<int> G[MAX];
int fa[MAX];
int vis[MAX];//判斷某點是不是回溯到的,如果是回溯到的就進行查詢操作
int n, q1, q2;

void init()
{
    for(int i = 1; i <= n; i++){
        vis[i] = 0;
        G[i].clear();
    }
}

int find(int x)//尋找祖先節點
{
    while(x != fa[x])
        x = fa[x];
    return x;
}

void tarjan(int u)
{
    vis[u] = 1;
    fa[u] = u;
    for(int i = 0; i < G[u].size(); i++){
        int v = G[u][i];
        if(!vis[v]){
            tarjan(v);
            fa[v] = u;
        }
    }
    if(u == q1 || u == q2){//這裡假設查詢q1和q2兩點
        if(u == q1 && vis[q2])
            ans = find(q2);
        else if(u == q2 && vis[q1])
            ans = find(q1);
    }
}


經典例題


Nearest Common Ancestors
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 24999 Accepted: 12984

Description

A rooted tree is a well-known data structure in computer science and engineering. An example is shown below:


In the figure, each node is labeled with an integer from {1, 2,...,16}. Node 8 is the root of the tree. Node x is an ancestor of node y if node x is in the path between the root and node y. For example, node 4 is an ancestor of node 16. Node 10 is also an ancestor of node 16. As a matter of fact, nodes 8, 4, 10, and 16 are the ancestors of node 16. Remember that a node is an ancestor of itself. Nodes 8, 4, 6, and 7 are the ancestors of node 7. A node x is called a common ancestor of two different nodes y and z if node x is an ancestor of node y and an ancestor of node z. Thus, nodes 8 and 4 are the common ancestors of nodes 16 and 7. A node x is called the nearest common ancestor of nodes y and z if x is a common ancestor of y and z and nearest to y and z among their common ancestors. Hence, the nearest common ancestor of nodes 16 and 7 is node 4. Node 4 is nearer to nodes 16 and 7 than node 8 is.

For other examples, the nearest common ancestor of nodes 2 and 3 is node 10, the nearest common ancestor of nodes 6 and 13 is node 8, and the nearest common ancestor of nodes 4 and 12 is node 4. In the last example, if y is an ancestor of z, then the nearest common ancestor of y and z is y.

Write a program that finds the nearest common ancestor of two distinct nodes in a tree.

Input

The input consists of T test cases. The number of test cases (T) is given in the first line of the input file. Each test case starts with a line containing an integer N , the number of nodes in a tree, 2<=N<=10,000. The nodes are labeled with integers 1, 2,..., N. Each of the next N -1 lines contains a pair of integers that represent an edge --the first integer is the parent node of the second integer. Note that a tree with N nodes has exactly N - 1 edges. The last line of each test case contains two distinct integers whose nearest common ancestor is to be computed.

Output

Print exactly one line for each test case. The line should contain the integer that is the nearest common ancestor.

Sample Input

2
16
1 14
8 5
10 16
5 9
4 6
8 4
4 10
1 13
6 15
10 11
6 7
10 2
16 3
8 1
16 12
16 7
5
2 3
3 4
3 1
1 5
3 5

Sample Output

4
3


這題唯一要注意的是:入度為0的根節點不確定,所以要用in陣列來判斷

#include <cstdio>
#include <vector>
#include <string.h>
using namespace std;
int const MAX = 10005;
vector<int> G[MAX];
int fa[MAX];
int in[MAX], vis[MAX];
int n;
int q1, q2, ans;
int find(int x)
{
    while(x != fa[x])
        x = fa[x];
    return x;
}

void tarjan(int u)
{
    vis[u] = 1;
    fa[u] = u;
    for(int i = 0; i < G[u].size(); i++){
        int v = G[u][i];
        if(!vis[v]){
            tarjan(v);
            fa[v] = u;
        }
    }
    if(u == q1 || u == q2){
        if(u == q1 && vis[q2])
            ans = find(q2);
        else if(u == q2 && vis[q1])
            ans = find(q1);
    }
}

int main()
{
    int cases;
    scanf("%d", &cases);
    while(cases--){
        memset(vis, 0, sizeof(vis));
        memset(in, 0, sizeof(in));
        scanf("%d", &n);
        for(int i = 1; i <= n; i++)
            G[i].clear();
        for(int i = 1; i < n; i++){
            int u, v;
            scanf("%d %d", &u, &v);
            in[v]++;
            G[u].push_back(v);
            G[v].push_back(u);
        }
        scanf("%d %d", &q1, &q2);
        for(int i = 1; i < n; i++){
            if(!in[i]){
                tarjan(i);
                break;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}


未完待續。。。