📜  Lary树的LCA |常数查询O(1)

📅  最后修改于: 2021-04-17 11:59:47             🧑  作者: Mango

我们已经看到了各种具有不同时间复杂度的方法来计算n元树中的LCA:-
方法1:天真的方法(通过计算从根到节点的路径)每个查询O(n)
方法2:使用Sqrt分解| O(sqrt H)
方法3:使用稀疏矩阵DP方法O(登录)

让我们研究另一种比上述所有方法具有更快查询时间的方法。因此,我们的目标是在恒定时间〜O(1)中计算LCA。让我们看看如何实现它。

方法4:使用范围最小查询

我们已经讨论了二叉树的LCA和RMQ。在这里,我们讨论n元树的LCA问题到RMQ问题的转换。

Pre-requisites:- LCA in Binary Tree using RMQ
                 RMQ using sparse table

第一步是将树分解为平面线性数组。为此,我们可以应用Euler步行。欧拉游走将给出图的预遍历。因此,我们将在树上执行欧拉遍历,并在访问节点时将其存储在数组中。此过程将树的数据结构简化为简单的线性数组。

考虑下面的那棵树,而欧拉就越过它:-

16933693_1309372792480521_1797138248_n

现在,让我们大致考虑一下:考虑树上的任何两个节点。将只有一条路径连接两个节点,并且路径中深度值最小的节点将是两个给定节点的LCA。
现在,在Euler Walk数组中取任意两个不同的节点,例如uv 。现在,从u到v的路径中的所有元素都将位于Euler walk数组中的u和v节点的索引之间。因此,我们只需要计算在Euler数组中节点u和节点v的索引之间的深度最小的节点即可。

为此,我们将维护另一个数组,其中包含所有节点的深度(与它们在欧拉步行数组中的位置相对应),以便可以在其上应用我们的RMQ算法。

下面给出的是与深度轨道阵列平行的欧拉步行阵列。

16901489_1309372785813855_1903972436_n

示例:-考虑欧拉阵列中的两个节点,节点6节点7 。为了计算节点6和节点7的LCA,我们寻找节点6和节点7之间的所有节点的最小深度值。
因此,节点1的最小深度值= 0 ,因此,它是节点6和节点7的LCA。

16934185_1309372782480522_1333490382_n

执行:-

We will be maintaining three arrays 1)Euler Path   
                                    2)Depth array   
                                    3)First Appearance Index

欧拉路径和深度数组与上述相同

首次出现索引FAI []:第一次出现索引数组将存储欧拉路径数组中每个节点的第一位置的索引。 FAI [i] =欧拉遍历数组中第i个节点的首次出现。

上述方法的实现如下:

C++
// C++ program to demonstrate LCA of n-ary tree
// in constant time.
#include "bits/stdc++.h"
using namespace std;
#define sz 101
 
vector < int > adj[sz];    // stores the tree
vector < int > euler;      // tracks the eulerwalk
vector < int > depthArr;   // depth for each node corresponding
                           // to eulerwalk
 
int FAI[sz];     // stores first appearence index of every node
int level[sz];   // stores depth for all nodes in the tree
int ptr;         // pointer to euler walk
int dp[sz][18];  // sparse table
int logn[sz];    // stores log values
int p2[20];      // stores power of 2
 
void buildSparseTable(int n)
{
    // initializing sparse table
    memset(dp,-1,sizeof(dp));
 
    // filling base case values
    for (int i=1; idepthArr[i-1])?i-1:i;
 
    // dp to fill sparse table
    for (int l=1; l<15; l++)
      for (int i=0; idepthArr[dp[i+p2[l-1]][l-1]])?
             dp[i+p2[l-1]][l-1] : dp[i][l-1];
        else
             break;
}
 
int query(int l,int r)
{
    int d = r-l;
    int dx = logn[d];
    if (l==r) return l;
    if (depthArr[dp[l][dx]] > depthArr[dp[r-p2[dx]][dx]])
        return dp[r-p2[dx]][dx];
    else
        return dp[l][dx];
}
 
void preprocess()
{
    // memorizing powers of 2
    p2[0] = 1;
    for (int i=1; i<18; i++)
        p2[i] = p2[i-1]*2;
 
    // memorizing all log(n) values
    int val = 1,ptr=0;
    for (int i=1; i FAI[v])
       swap(u,v);
 
    // doing RMQ in the required range
    return euler[query(FAI[u], FAI[v])];
}
 
void addEdge(int u,int v)
{
    adj[u].push_back(v);
    adj[v].push_back(u);
}
 
int main(int argc, char const *argv[])
{
    // constructing the described tree
    int numberOfNodes = 8;
    addEdge(1,2);
    addEdge(1,3);
    addEdge(2,4);
    addEdge(2,5);
    addEdge(2,6);
    addEdge(3,7);
    addEdge(3,8);
 
    // performing required precalculations
    preprocess();
 
    // doing the Euler walk
    ptr = 0;
    memset(FAI,-1,sizeof(FAI));
    dfs(1,0,0);
 
    // creating depthArray corresponding to euler[]
    makeArr();
 
    // building sparse table
    buildSparseTable(depthArr.size());
 
    cout << "LCA(6,7) : " << LCA(6,7) << "\n";
    cout << "LCA(6,4) : " << LCA(6,4) << "\n";
 
    return 0;
}


Java
// Java program to demonstrate LCA of n-ary
// tree in constant time.
import java.util.ArrayList;
import java.util.Arrays;
 
class GFG{
 
static int sz = 101;
 
@SuppressWarnings("unchecked")
// Stores the tree
static ArrayList[] adj = new ArrayList[sz];
 
// Tracks the eulerwalk
static ArrayList euler = new ArrayList<>();
 
// Depth for each node corresponding
static ArrayList depthArr = new ArrayList<>();
// to eulerwalk
 
// Stores first appearence index of every node
static int[] FAI = new int[sz];
 
// Stores depth for all nodes in the tree
static int[] level = new int[sz];
 
// Pointer to euler walk
static int ptr;
 
// Sparse table
static int[][] dp = new int[sz][18];
 
// Stores log values
static int[] logn = new int[sz];
 
// Stores power of 2
static int[] p2 = new int[20];
 
static void buildSparseTable(int n)
{
     
    // Initializing sparse table
    for(int i = 0; i < sz; i++)
    {
        for(int j = 0; j < 18; j++)
        {
            dp[i][j] = -1;
        }
    }
 
    // Filling base case values
    for(int i = 1; i < n; i++)
        dp[i - 1][0] = (depthArr.get(i) >
                        depthArr.get(i - 1)) ?
                                     i - 1 : i;
 
    // dp to fill sparse table
    for(int l = 1; l < 15; l++)
        for(int i = 0; i < n; i++)
            if (dp[i][l - 1] != -1 &&
               dp[i + p2[l - 1]][l - 1] != -1)
                dp[i][l] = (depthArr.get(dp[i][l - 1]) >
                            depthArr.get(
                                dp[i + p2[l - 1]][l - 1])) ?
                                dp[i + p2[l - 1]][l - 1] :
                                dp[i][l - 1];
            else
                break;
}
 
static int query(int l, int r)
{
    int d = r - l;
    int dx = logn[d];
     
    if (l == r)
        return l;
         
    if (depthArr.get(dp[l][dx]) >
        depthArr.get(dp[r - p2[dx]][dx]))
        return dp[r - p2[dx]][dx];
    else
        return dp[l][dx];
}
 
static void preprocess()
{
     
    // Memorizing powers of 2
    p2[0] = 1;
    for(int i = 1; i < 18; i++)
        p2[i] = p2[i - 1] * 2;
 
    // Memorizing all log(n) values
    int val = 1, ptr = 0;
    for(int i = 1; i < sz; i++)
    {
        logn[i] = ptr - 1;
        if (val == i)
        {
            val *= 2;
            logn[i] = ptr;
            ptr++;
        }
    }
}
 
// Euler Walk ( preorder traversal) converting
// tree to linear depthArray
// Time Complexity : O(n)
static void dfs(int cur, int prev, int dep)
{
     
    // Marking FAI for cur node
    if (FAI[cur] == -1)
        FAI[cur] = ptr;
 
    level[cur] = dep;
 
    // Pushing root to euler walk
    euler.add(cur);
 
    // Incrementing euler walk pointer
    ptr++;
 
    for(Integer x : adj[cur])
    {
        if (x != prev)
        {
            dfs(x, cur, dep + 1);
 
            // Pushing cur again in backtrack
            // of euler walk
            euler.add(cur);
 
            // Increment euler walk pointer
            ptr++;
        }
    }
}
 
// Create Level depthArray corresponding
// to the Euler walk Array
static void makeArr()
{
    for(Integer x : euler)
        depthArr.add(level[x]);
}
 
static int LCA(int u, int v)
{
     
    // Trival case
    if (u == v)
        return u;
 
    if (FAI[u] > FAI[v])
    {
        int temp = u;
        u = v;
        v = temp;
    }
 
    // Doing RMQ in the required range
    return euler.get(query(FAI[u], FAI[v]));
}
 
static void addEdge(int u, int v)
{
    adj[u].add(v);
    adj[v].add(u);
}
 
// Driver code
public static void main(String[] args)
{
    for(int i = 0; i < sz; i++)
    {
        adj[i] = new ArrayList<>();
    }
     
    // Constructing the described tree
    int numberOfNodes = 8;
    addEdge(1, 2);
    addEdge(1, 3);
    addEdge(2, 4);
    addEdge(2, 5);
    addEdge(2, 6);
    addEdge(3, 7);
    addEdge(3, 8);
 
    // Performing required precalculations
    preprocess();
 
    // Doing the Euler walk
    ptr = 0;
    Arrays.fill(FAI, -1);
    dfs(1, 0, 0);
 
    // Creating depthArray corresponding to euler[]
    makeArr();
     
    // Building sparse table
    buildSparseTable(depthArr.size());
 
    System.out.println("LCA(6,7) : " + LCA(6, 7));
    System.out.println("LCA(6,4) : " + LCA(6, 4));
}
}
 
// This code is contributed by sanjeev2552


输出:

LCA(6,7) : 1
LCA(6,4) : 2

注意:我们正在预先计算所有2所需的幂,并且还预先计算所有必需的日志值以确保每个查询的时间复杂度恒定。否则,如果我们为每个查询操作进行日志计算,那么时间复杂度就不会保持恒定。

时间复杂度:从LCA到RMQ的转换过程由Euler Walk完成,需要O(n)时间。
在RMQ中对稀疏表进行预处理需要O(nlogn)时间,回答每个查询都是一个固定时间过程。因此,总体时间复杂度为O(nlogn)-预处理和每个查询的O(1)