最近公共祖先(LCA) 向上標記法 & 樹上倍增法 & Tarjan算法

  • 求最近公共祖先的算法

一.向上標記法

從x向上走到根節點, 並標記路徑上經過的點
從y向上走到根節點, 當遇到第一個被標記的點就找到了LCA(x, y)

二.樹上倍增發

時間複雜度: O(mlogn)
    用 f[x, k] 表示x向上走2^k到達的節點, 很容易知道 , f[x, 0] 就是x的父節點, 遞推關係就是, f[x, k] = f[ f[x, k - 1], k - 1]
    用depth[x], 存x的深度

步驟
1.預處理 f 數組和 depth 數組
2.實現目標函數: 保證 depth[x] > depth[y], 如果不是, 就想換x, y的值
3.用二進制拆分的思想, 把 x 調到和 y 同一個深度
4.如果此時 x == y, 那麼y就是他們的公共祖先
5. 如果不相等,就把x 和 y 同時往上調, 並且保證他們不會相遇
6. 這樣我們到達的就是他們的LCA的子節點, 最後結果就是f[x, 0]

對應步驟的代碼

1.預處理部分
用bfs實現, 深度就是當前節點的父節點深度+1, f 數組就是利用那個遞推式實現, 這裏的15是取值範圍的最大值的位數, 由題目而定

void bfs()  //步驟1 預處理
{
    memset(depth, 0x3f, sizeof depth);
    depth[root] = 1;
    depth[0] = 0;
    queue<int> q;
    q.push(root);
    while(q.size())
    {
        int x = q.front();
        q.pop();
        for(int i = h[x]; ~i; i = ne[i])
        {
            int y = e[i];
            if(depth[y] > depth[x] + 1)
            {
                depth[y] = depth[x] + 1;
                q.push(y);
                f[y][0] = x;
                for(int j = 1; j <= 15; j++)
                    f[y][j] = f[f[y][j - 1]][j - 1];
            }
        }
    }
}

2.lca函數部分
這裏是求 x, y 的LCA

int lca(int x, int y)
{
    
    if(depth[x] > depth[y])  //步驟2
        swap(x, y);
    
    for(int i = 15; i >= 0; i--)  // 步驟3
    {
        if(depth[f[y][i]] >= depth[x])
            y = f[y][i];
    }
    if(x == y) return x; //步驟4
    
    for(int i = 15; i >= 0; i--)  //步驟5
    {
        if(depth[f[x][i]] != depth[f[y][i]])
        {
            x = f[x][i];
            y = f[y][i];
        }
        
    }
    return f[y][0]; //步驟6 返回最後結果
}

三:Tarjan 算法

    時間複雜度:O(n + m)
    該算法的本質是用並查集對向上標記法進行優化, 達到線性的時間複雜度
在實現算法的過程中, 我們把樹中的節點分爲3類:

  1. 正在訪問的節點, 以及它的到根節點這條路徑上的節點, 標記爲1
  2. 已經訪問完畢, 並且已經回溯的節點嗎,標記爲2
  3. 還沒有訪問的節點, 不標記

由於這是一種離線算法, 我們需要把詢問一次性全部讀入, 然後再執行算法

3中節點解釋
在這裏插入圖片描述
代碼實現

void tarjan(int x)
{
    vis[x] = 1;
    for(int i = h[x]; ~i; i = ne[i])
    {
        int y = e[i];
        if(!vis[y])
        {
            tarjan(y);
            f[y] = x;
        }
    }
    
    for(int i = 0; i < q[x].size(); i++)
    {
        PII t = q[x][i];
        int y = t.first;
        int id = t.second;
        if(vis[y] == 2)
        {
            int father = find(y);
           //此時這個father就是LCA答案
        }
    }
    vis[x] = 2;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章