Lca相關演算法

Jamence發表於2018-08-05

這裡寫圖片描述

Lca相關演算法

簡介

在圖論和電腦科學中,最近公共祖先是指在一個樹或者有向無環圖中同時擁有v和w作為後代的最深的節點。在這裡,我們定義一個節點也是其自己的後代,因此如果v是w的後代,那麼w就是v和w的最近公共祖先。

最近公共祖先是兩個節點所有公共祖先中離根節點最遠的,計算最近公共祖先和根節點的長度往往是有用的。比如為了計算樹中兩個節點v和w之間的距離,可以使用以下方法:分別計算由v到根節點和w到根節點的距離,兩者之和減去最近公共祖先到根節點的距離的兩倍即可得到v到w的距離。

求解方法

線上演算法ST表

線上演算法和離線演算法:
在電腦科學中,一個線上演算法是指它可以以序列化的方式一個個的處理輸入,也就是說在開始時並不需要已經知道所有的輸入。相對的,對於一個離線演算法,在開始時就需要知道問題的所有輸入資料,而且在解決一個問題後就要立即輸出結果。例如,選擇排序在排序前就需要知道所有待排序元素,然而插入排序就不必。
首先我們們對於以下一棵樹
這裡寫圖片描述

分析如下
這裡寫圖片描述
在轉化成區間之後,我們可以使用RMQ相關演算法來解決該問題。
對於dfs演算法:

int tot,head[N],ver[2*N],R[2*N],first[N],dir[N];
//ver:節點編號 R:深度 first:點編號第一次位置 dir:距離
void dfs(int u ,int dep)
{
       vis[u] = true;
    ver[++tot] = u;
    first[u] = tot;
    R[tot] = dep;
    for(int k=head[u]; k!=-1; k=e[k].next)
        if( !vis[e[k].v] )
        {
            int v = e[k].v , w = e[k].w;
            dir[v] = dir[u] + w;
            dfs(v,dep+1);
            //下面兩句表示dfs的時候還要回溯到上面
            ver[++tot] = u;
            R[tot] = dep;
        }
}

假設我們需要得到D和F的LCA,
那麼我們找D和F在序列中的位置可以通過first陣列發現在陣列下標為3—–6,在該區間中找到深度為2,該點的編號為4,即為B點(得出區間後,我們使用RMQ維護)

void ST(int n)
{
    for(int i=1;i<=n;i++)
        dp[i][0] = i;
    for(int j=1;(1<<j)<=n;j++)
    {
        for(int i=1;i+(1<<j)-1<=n;i++)
        {
            int a = dp[i][j-1] , b = dp[i+(1<<(j-1))][j-1];
            dp[i][j] = R[a]<R[b]?a:b;
        }
    }
}
//中間部分是交叉的。
int RMQ(int l,int r)
{
    int k=0;
    while((1<<(k+1))<=r-l+1)
        k++;
    int a = dp[l][k], b = dp[r-(1<<k)+1][k]; //儲存的是編號
    return R[a]<R[b]?a:b;
}

int LCA(int u ,int v)
{
    int x = first[u] , y = first[v];
    if(x > y) swap(x,y);
    int res = RMQ(x,y);
    return ver[res];
}

Tarjan離線演算法

tarjan演算法的有點在於相對穩定,時間複雜度也比較居中,比較容易理解
基本思路是:

1.任選一個點為根節點,從根節點開始。
2.遍歷該點u所有子節點v,並標記這些子節點v已被訪問過。
3.若是v還有子節點,返回2,否則下一步。
4.合併v到u上。
5.尋找與當前點u有詢問關係的點v。
6.若是v已經被訪問過了,則可以確認u和v的最近公共祖先為v被合併到的父親節點a。
虛擬碼如下:

Tarjan(u)//marge和find為並查集合並函式和查詢函式
{
    for each(u,v)    //訪問所有u子節點v
    {
        Tarjan(v);        //繼續往下遍歷
        marge(u,v);    //合併v到u上
        標記v被訪問過;
    }
    for each(u,e)    //訪問所有和u有詢問關係的e
    {
        如果e被訪問過;
        u,e的最近公共祖先為find(e);
    }
}

完整程式碼:

#include <iostream>
#include <vector>
using namespace std;

const int MAXN = 1001;
vector<int> tree[MAXN];
int indegree[MAXN], ancestor[MAXN];
int nvertex, root;
int father[MAXN], rnk[MAXN];
bool visited[MAXN];
int nquery;
vector<int> query[MAXN];

void init()
{
    for(int i=0; i<nvertex; ++i){
        tree[i].clear();
        indegree[i] = 0;
        ancestor[i] = i;
        father[i] = i;
        rnk[i] = 0;
        visited[i] = false;
        query[i].clear();
    }
}

/*
int find(int x)
{
 int rt = x;
    while(rt != father[rt])
        rt = father[rt];

    while(father[x] != rt){
        int y = father[x];
        father[x] = rt;
        x = y;
    }

    return rt;
}
*/


int find(int x)
{
    if(x != father[x])
        father[x] = find(father[x]);
    return father[x];
}


void unin(int x, int y)
{
    x = find(x), y = find(y);
    if(x == y)  return ;
    if(rnk[x] > rnk[y]) father[y] = x;
 else father[x] = y, rnk[y] += rnk[x] == rnk[y];
}

void tarjan(int x)
{
    for(int i=0; i<tree[x].size(); ++i){
        tarjan(tree[x][i]);
        unin(x, tree[x][i]);
        ancestor[find(x)] = x;
    }

    visited[x] = true;

    for(int i=0; i<query[x].size(); ++i){
        if(visited[query[x][i]])
            printf("the LCA for %d and %d is %d\n", x, query[x][i], ancestor[find(query[x][i])]);
    }
}

int main()
{
    scanf("%d", &nvertex);
    init();

    int x, y;
    for(int i=1; i<nvertex; ++i){
        scanf("%d%d", &x, &y);
        tree[x].push_back(y); //x->y
        indegree[y]++;
 }

    scanf("%d", &nquery);
    for(int i=0; i<nquery; ++i){
        scanf("%d%d", &x, &y);
        query[x].push_back(y);
        query[y].push_back(x);
    }

    for(int i=0; i<nvertex; ++i)
        if(indegree[i] == 0) { root = i; break; }

    tarjan(root);

    return 0;
}

  1. Lca介紹https://zh.wikipedia.org/wiki/%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88_(%E5%9B%BE%E8%AE%BA)
  2. 線上演算法ST表https://blog.csdn.net/liangzhaoyang1/article/details/52549822
  3. Tarjan演算法https://www.cnblogs.com/JVxie/p/4854719.html
  4. Tarjan演算法實現https://blog.csdn.net/freeelinux/article/details/54974044

相關文章