Open In App

LCA for n-ary Tree | Constant Query O(1)

Improve
Improve
Like Article
Like
Save
Share
Report

We have seen various methods with different Time Complexities to calculate LCA in n-ary tree:-

Method 1 : Naive Method ( by calculating root to node path) | O(n) per query 
Method 2 :Using Sqrt Decomposition | O(sqrt H) 
Method 3 : Using Sparse Matrix DP approach | O(logn) 

Lets study another method that has faster query time than all the above methods. So, our aim will be to calculate LCA in constant time ~ O(1). Let’s see how we can achieve it. 

Method 4 : Using Range Minimum Query 

We have discussed LCA and RMQ for binary tree. Here we discuss LCA problem to RMQ problem conversion for n-ary tree. 

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

Key Concept : In this method, we will be reducing our LCA problem to RMQ(Range Minimum Query) problem over a static array. Once, we do that then we will relate the Range minimum queries to the required LCA queries. 

The first step will be to decompose the tree into a flat linear array. To do this we can apply the Euler walk. The Euler walk will give the pre-order traversal of the graph. So we will perform a Euler Walk on the tree and store the nodes in an array as we visit them. This process reduces the tree data-structure to a simple linear array. 

Consider the below tree and the euler walk over it:- 

16933693_1309372792480521_1797138248_n

Now lets think in general terms : Consider any two nodes on the tree. There will be exactly one path connecting both the nodes and the node that has the smallest depth value in the path will be the LCA of the two given nodes.
Now take any two distinct node say u and v in the Euler walk array. Now all the elements in the path from u to v will lie in between the index of nodes u and v in the Euler walk array. Therefore, we just need to calculate the node with the minimum depth between the index of node u and node v in the euler array. 

For this we will maintain another array that will contain the depth of all the nodes corresponding to their position in the Euler walk array so that we can Apply our RMQ algorithm on it.

Given below is the euler walk array parallel to its depth track array. 

16901489_1309372785813855_1903972436_n

Example :- Consider two nodes node 6 and node 7 in the euler array. To calculate the LCA of node 6 and node 7 we look the smallest depth value for all the nodes in between node 6 and node 7 . 
Therefore, node 1 has the smallest depth value = 0 and hence, it is the LCA for node 6 and node 7.

16934185_1309372782480522_1333490382_n

Implementation:- 

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

Euler Path and Depth array are the same as described above

First Appearance Index FAI[] : The First Appearance index Array will store the index for the first position of every node in the Euler Path array. FAI[i] = First appearance of ith node in Euler Walk array. 

The Implementation for the above method is given below:-

Implementation:

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 appearance 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; i<n; i++)
        dp[i-1][0] = (depthArr[i]>depthArr[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 and dp[i+p2[l-1]][l-1]!=-1)
          dp[i][l] =
            (depthArr[dp[i][l-1]]>depthArr[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<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)
 * */
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.push_back(cur);
 
    // incrementing euler walk pointer
    ptr++;
 
    for (auto x:adj[cur])
    {
        if (x != prev)
        {
            dfs(x,cur,dep+1);
 
            // pushing cur again in backtrack
            // of euler walk
            euler.push_back(cur);
 
            // increment euler walk pointer
            ptr++;
        }
    }
}
 
// Create Level depthArray corresponding
// to the Euler walk Array
void makeArr()
{
    for (auto x : euler)
        depthArr.push_back(level[x]);
}
 
int LCA(int u,int v)
{
    // trivial case
    if (u==v)
       return u;
 
    if (FAI[u] > 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<Integer>[] adj = new ArrayList[sz];
 
// Tracks the eulerwalk
static ArrayList<Integer> euler = new ArrayList<>();
 
// Depth for each node corresponding
static ArrayList<Integer> depthArr = new ArrayList<>();
// to eulerwalk
 
// Stores first appearance 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)
{
     
    // Trivial 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


Python3




# Python program to demonstrate LCA of n-ary tree
# in constant time.
 
from typing import List
 
# stores the tree
adj = [[] for _ in range(101)]
# tracks the eulerwalk
euler = []
# depth for each node corresponding to eulerwalk
depthArr = []
# stores first appearance index of every node
FAI = [-1] * 101
# stores depth for all nodes in the tree
level = [0] * 101
# pointer to euler walk
ptr = 0
# sparse table
dp = [[-1] * 18 for _ in range(101)]
# stores log values
logn = [0] * 101
# stores power of 2
p2 = [0] * 20
 
 
def buildSparseTable(n: int):
    # initializing sparse table
    for i in range(n):
        dp[i][0] = i-1 if depthArr[i] > depthArr[i-1] else i
 
    # dp to fill sparse table
    for l in range(1, 15):
        for i in range(n):
            if dp[i][l-1] != -1 and dp[i+p2[l-1]][l-1] != -1:
                dp[i][l] = dp[i+p2[l-1]][l-1] if depthArr[dp[i][l-1]
                                                          ] > depthArr[dp[i+p2[l-1]][l-1]] else dp[i][l-1]
            else:
                break
 
 
def query(l: int, r: int) -> int:
    d = r-l
    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]
 
 
def preprocess():
    global ptr
    # memorizing powers of 2
    p2[0] = 1
    for i in range(1, 18):
        p2[i] = p2[i-1]*2
 
    # memorizing all log(n) values
    val = 1
    ptr = 0
    for i in range(1, 101):
        logn[i] = ptr-1
        if val == i:
            val *= 2
            logn[i] = ptr
            ptr += 1
 
 
def dfs(cur: int, prev: int, dep: int):
    global ptr
    # marking FAI for cur node
    if FAI[cur] == -1:
        FAI[cur] = ptr
 
    level[cur] = dep
 
    # pushing root to euler walk
    euler.append(cur)
 
    # incrementing euler walk pointer
    ptr += 1
 
    for x in adj[cur]:
        if x != prev:
            dfs(x, cur, dep+1)
 
            # pushing cur again in backtrack
            # of euler walk
            euler.append(cur)
 
            # increment euler walk pointer
            ptr += 1
 
 
# Create Level depthArray corresponding
# to the Euler walk Array
def makeArr():
    global depthArr
    for x in euler:
        depthArr.append(level[x])
 
 
def LCA(u: int, v: int) -> int:
    # trivial case
    if u == v:
        return u
 
    if FAI[u] > FAI[v]:
        u, v = v, u
 
    # doing RMQ in the required range
    return euler[query(FAI[u], FAI[v])]
 
 
def addEdge(u, v):
    adj[u].append(v)
    adj[v].append(u)
 
 
# constructing the described tree
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
FAI = [-1] * (numberOfNodes + 1)
dfs(1, 0, 0)
 
# creating depthArray corresponding to euler[]
makeArr()
 
# building sparse table
buildSparseTable(len(depthArr))
 
print("LCA(6,7) : ", LCA(6, 7))
print("LCA(6,4) : ", LCA(6, 4))


C#




// C# program to demonstrate LCA of n-ary
// tree in constant time.
 
using System;
using System.Collections.Generic;
 
public class GFG {
    static int sz = 101;
    // Stores the tree
    static List<int>[] adj = new List<int>[sz];
     
    // Tracks the eulerwalk
    static List<int> euler = new List<int>();
         
    // Depth for each node corresponding
    static List<int> depthArr = new List<int>();
     
    // to eulerwalk
 
    // Stores first appearance 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[i] > depthArr[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[dp[i,l - 1]] > depthArr[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[dp[l,dx]] > depthArr[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++;
     
        foreach (int x in adj[cur])
        {
            if (x != prev)
            {
                dfs(x, cur, dep + 1);
     
                euler.Add(cur);
     
                ptr++;
            }
        }
    }
     
    // Create Level depthArray corresponding
    // to the Euler walk Array
    static void makeArr()
    {
        foreach (int x in euler)
            depthArr.Add(level[x]);
    }
     
    static int LCA(int u, int v)
    {
        // Trivial 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[query(FAI[u], FAI[v])];
    }
     
    static void addEdge(int u, int v)
    {
        adj[u].Add(v);
        adj[v].Add(u);
    }
 
    // Driver Code
    static void Main(string[] args)
    {
        int sz = 9;
        adj = new List<int>[sz];
        for (int i = 0; i < sz; i++)
        {
            adj[i] = new List<int>();
        }
 
        // 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;
        Array.Fill(FAI, -1);
        dfs(1, 0, 0);
 
        // Creating depthArray corresponding to euler[]
        makeArr();
 
        // Building sparse table
        buildSparseTable(depthArr.Count);
 
        Console.WriteLine("LCA(6,7) : " + LCA(6, 7));
        Console.WriteLine("LCA(6,4) : " + LCA(6, 4));
    }
     
}
// This code is contributed by Prince Kumar


Javascript




let adj = [];
for (let _ = 0; _ < 101; _++) {
  adj.push([]);
}
 
// tracks the eulerwalk
let euler = [];
 
// depth for each node corresponding to eulerwalk
let depthArr = [];
 
// stores first appearance index of every node
let FAI = new Array(101).fill(-1);
 
// stores depth for all nodes in the tree
let level = new Array(101).fill(0);
 
// pointer to euler walk
let ptr = 0;
 
// sparse table
let dp = [];
for (let _ = 0; _ < 101; _++) {
  dp.push(new Array(18).fill(-1));
}
 
// stores log values
let logn = new Array(101).fill(0);
 
// stores power of 2
let p2 = new Array(20).fill(0);
 
function buildSparseTable(n)
{
 
  // initializing sparse table
  for (let i = 0; i < n; i++) {
    dp[i][0] = i - 1 >= 0 && depthArr[i] > depthArr[i - 1] ? i - 1 : i;
  }
 
  // dp to fill sparse table
  for (let l = 1; l < 15; l++) {
    for (let i = 0; i < n; i++) {
      if (
        dp[i][l - 1] !== -1 &&
        dp[i + p2[l - 1]][l - 1] !== -1
      ) {
        dp[i][l] =
          depthArr[dp[i][l - 1]] >
          depthArr[dp[i + p2[l - 1]][l - 1]]
            ? dp[i + p2[l - 1]][l - 1]
            : dp[i][l - 1];
      } else {
        break;
      }
    }
  }
}
 
function query(l, r) {
  let d = r - l;
  let 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];
  }
}
 
function preprocess() {
  // memorizing powers of 2
  p2[0] = 1;
  for (let i = 1; i < 18; i++) {
    p2[i] = p2[i - 1] * 2;
  }
 
  // memorizing all log(n) values
  let val = 1;
  ptr = 0;
  for (let i = 1; i < 101; i++) {
    logn[i] = ptr - 1;
    if (val === i) {
      val *= 2;
      logn[i] = ptr;
      ptr += 1;
    }
  }
}
 
function dfs(cur, prev, dep) {
  // marking FAI for cur node
  if (FAI[cur] === -1) {
    FAI[cur] = ptr;
  }
 
  level[cur] = dep;
 
  // pushing root to euler walk
  euler.push(cur);
 
  // incrementing euler walk pointer
  ptr += 1;
 
  for (let x of adj[cur]) {
    if (x !== prev) {
      dfs(x, cur, dep + 1);
 
      // pushing cur again in backtrack
      // of euler walk
      euler.push(cur);
 
      // increment euler walk pointer
      ptr += 1;
    }
  }
}
 
// Create Level depthArray corresponding
// to the Euler walk Array
function makeArr() {
  for (let x of euler) {
    depthArr.push(level[x]);
  }
}
 
function LCA(u, v) {
  // trivial case
  if (u === v) {
    return u;
  }
  if (FAI[u] > FAI[v]) {
    [u, v] = [v, u];
  }
 
  // doing RMQ in the required range
  return euler[query(FAI[u], FAI[v])];
}
 
function addEdge(u, v) {
  adj[u].push(v);
  adj[v].push(u);
}
 
// constructing the described tree
let 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;
FAI = new Array(numberOfNodes + 1).fill(-1);
dfs(1, 0, 0);
 
// creating depthArray corresponding to euler[]
makeArr();
 
// building sparse table
buildSparseTable(depthArr.length);
 
console.log("LCA(6,7) : ", LCA(6, 7));
console.log("LCA(6,4) : ", LCA(6, 4));


Output

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

Note : We are precalculating all the required power of 2’s and also precalculating the all the required log values to ensure constant time complexity per query. Else if we did log calculation for every query operation our Time complexity would have not been constant.

Time Complexity: The Conversion process from LCA to RMQ is done by Euler Walk that takes O(n) time. 
Pre-processing for the sparse table in RMQ takes O(nlogn) time and answering each Query is a Constant time process. Therefore, overall Time Complexity is O(nlogn) – preprocessing and O(1) for each query.

Auxiliary Space: O(n+sz)

 



Last Updated : 27 Apr, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads