LCA in a tree using Binary Lifting Technique
Given a binary tree, the task is to find the Lowest Common Ancestor of the given two nodes in the tree.
Let G be a tree then LCA of two nodes u and v is defined as the node w in the tree which is an ancestor of both u and v and is farthest from the root node.If one node is the ancestor of another one than that particular node is the LCA of those two nodes.
Example:
Input:
Output:
The LCA of 6 and 9 is 1.
The LCA of 5 and 9 is 1.
The LCA of 6 and 8 is 3.
The LCA of 6 and 1 is 1.
Approach: The article describes an approach known as Binary Lifting to find the Lowest Common Ancestor of two nodes in a tree. There can be many approaches to solve the LCA problem. We are discussing the Binary Lifting Technique, the others can be read from here and here.
Binary Lifting is a dynamic programming approach where we pre-compute an array memo[1, n][1, log(n)] where memo[i][j] contains 2^j-th ancestor of node i. For computing the values of memo[][], the following recursion may be used
memo state:
memo[i][j] = i-th node’s 2^(j)th ancestor in the path
memo initialization:
memo[i][j] = memo[i][0] (first parent (2^0) of each node is given)
memo trans:
memo[i][j] = memo[ memo [i][j – 1]]
meaning: A(i,2^j)=A( A(i , 2^(j-1) ) , 2^(j-1) )
To find the (2^j)-th ancestor of i, recursively find i-th node’s 2^(j-1)th ancestor’s 2^(j-1)th ancestor. (2^(j) = 2^(j-1) + 2^(j-1))
So:
memo[i][j] = parent[i] if j = 0 and
memo[i][j] = memo[memo[i][j – 1]][j – 1] if j > 0.
We first check whether a node is an ancestor of other or not and if one node is ancestor of other then it is the LCA of these two nodes otherwise we find a node which is not the common ancestor of both u and v and is highest(i.e. a node x such that x is not the common ancestor of u and v but memo[x][0] is) in the tree. After finding such a node (let it be x), we print the first ancestor of x i.e. memo[x][0] which will be the required LCA.
Below is the implementation of the above approach:
C++
// C++ implementation of the approach #include <bits/stdc++.h> using namespace std; // Pre-processing to calculate values of memo[][] void dfs( int u, int p, int **memo, vector< int > &lev, int log , vector< int > *g) { // Using recursion formula to calculate // the values of memo[][] memo[u][0] = p; for ( int i = 1; i <= log ; i++) memo[u][i] = memo[memo[u][i - 1]][i - 1]; for ( int v : g[u]) { if (v != p) { lev[v] = lev[u] + 1; dfs(v, u, memo, lev, log , g); } } } // Function to return the LCA of nodes u and v int lca( int u, int v, int log , vector< int > &lev, int **memo) { // The node which is present farthest // from the root node is taken as u // If v is farther from root node // then swap the two if (lev[u] < lev[v]) swap(u, v); // Finding the ancestor of u // which is at same level as v for ( int i = log ; i >= 0; i--) if ((lev[u] - pow (2, i)) >= lev[v]) u = memo[u][i]; // If v is the ancestor of u // then v is the LCA of u and v if (u == v) return u; // Finding the node closest to the root which is // not the common ancestor of u and v i.e. a node // x such that x is not the common ancestor of u // and v but memo[x][0] is for ( int i = log ; i >= 0; i--) { if (memo[u][i] != memo[v][i]) { u = memo[u][i]; v = memo[v][i]; } } // Returning the first ancestor // of above found node return memo[u][0]; } // Driver Code int main() { // Number of nodes int n = 9; // vector to store tree vector< int > g[n + 1]; int log = ( int ) ceil (log2(n)); int **memo = new int *[n + 1]; for ( int i = 0; i < n + 1; i++) memo[i] = new int [ log + 1]; // Stores the level of each node vector< int > lev(n + 1); // Initialising memo values with -1 for ( int i = 0; i <= n; i++) memset (memo[i], -1, sizeof memo[i]); // Add edges g[1].push_back(2); g[2].push_back(1); g[1].push_back(3); g[3].push_back(1); g[1].push_back(4); g[4].push_back(1); g[2].push_back(5); g[5].push_back(2); g[3].push_back(6); g[6].push_back(3); g[3].push_back(7); g[7].push_back(3); g[3].push_back(8); g[8].push_back(3); g[4].push_back(9); g[9].push_back(4); dfs(1, 1, memo, lev, log , g); cout << "The LCA of 6 and 9 is " << lca(6, 9, log , lev, memo) << endl; cout << "The LCA of 5 and 9 is " << lca(5, 9, log , lev, memo) << endl; cout << "The LCA of 6 and 8 is " << lca(6, 8, log , lev, memo) << endl; cout << "The LCA of 6 and 1 is " << lca(6, 1, log , lev, memo) << endl; return 0; } // This code is contributed by // sanjeev2552 |
Java
// Java implementation of the approach import java.util.*; public class GFG { // ArrayList to store tree static ArrayList<Integer> g[]; static int memo[][], lev[], log; // Pre-processing to calculate values of memo[][] static void dfs( int u, int p) { // Using recursion formula to calculate // the values of memo[][] memo[u][ 0 ] = p; for ( int i = 1 ; i <= log; i++) memo[u][i] = memo[memo[u][i - 1 ]][i - 1 ]; for ( int v : g[u]) { if (v != p) { // Calculating the level of each node lev[v] = lev[u] + 1 ; dfs(v, u); } } } // Function to return the LCA of nodes u and v static int lca( int u, int v) { // The node which is present farthest // from the root node is taken as u // If v is farther from root node // then swap the two if (lev[u] < lev[v]) { int temp = u; u = v; v = temp; } // Finding the ancestor of u // which is at same level as v for ( int i = log; i >= 0 ; i--) { if ((lev[u] - ( int )Math.pow( 2 , i)) >= lev[v]) u = memo[u][i]; } // If v is the ancestor of u // then v is the LCA of u and v if (u == v) return u; // Finding the node closest to the root which is // not the common ancestor of u and v i.e. a node // x such that x is not the common ancestor of u // and v but memo[x][0] is for ( int i = log; i >= 0 ; i--) { if (memo[u][i] != memo[v][i]) { u = memo[u][i]; v = memo[v][i]; } } // Returning the first ancestor // of above found node return memo[u][ 0 ]; } // Driver code public static void main(String args[]) { // Number of nodes int n = 9 ; g = new ArrayList[n + 1 ]; // log(n) with base 2 log = ( int )Math.ceil(Math.log(n) / Math.log( 2 )); memo = new int [n + 1 ][log + 1 ]; // Stores the level of each node lev = new int [n + 1 ]; // Initialising memo values with -1 for ( int i = 0 ; i <= n; i++) Arrays.fill(memo[i], - 1 ); for ( int i = 0 ; i <= n; i++) g[i] = new ArrayList<>(); // Add edges g[ 1 ].add( 2 ); g[ 2 ].add( 1 ); g[ 1 ].add( 3 ); g[ 3 ].add( 1 ); g[ 1 ].add( 4 ); g[ 4 ].add( 1 ); g[ 2 ].add( 5 ); g[ 5 ].add( 2 ); g[ 3 ].add( 6 ); g[ 6 ].add( 3 ); g[ 3 ].add( 7 ); g[ 7 ].add( 3 ); g[ 3 ].add( 8 ); g[ 8 ].add( 3 ); g[ 4 ].add( 9 ); g[ 9 ].add( 4 ); dfs( 1 , 1 ); System.out.println( "The LCA of 6 and 9 is " + lca( 6 , 9 )); System.out.println( "The LCA of 5 and 9 is " + lca( 5 , 9 )); System.out.println( "The LCA of 6 and 8 is " + lca( 6 , 8 )); System.out.println( "The LCA of 6 and 1 is " + lca( 6 , 1 )); } } |
Python3
# Python3 implementation of the above approach import math # Pre-processing to calculate values of memo[][] def dfs(u, p, memo, lev, log, g): # Using recursion formula to calculate # the values of memo[][] memo[u][ 0 ] = p for i in range ( 1 , log + 1 ): memo[u][i] = memo[memo[u][i - 1 ]][i - 1 ] for v in g[u]: if v ! = p: lev[v] = lev[u] + 1 dfs(v, u, memo, lev, log, g) # Function to return the LCA of nodes u and v def lca(u, v, log, lev, memo): # The node which is present farthest # from the root node is taken as u # If v is farther from root node # then swap the two if lev[u] < lev[v]: swap(u, v) # Finding the ancestor of u # which is at same level as v for i in range (log, - 1 , - 1 ): if (lev[u] - pow ( 2 , i)) > = lev[v]: u = memo[u][i] # If v is the ancestor of u # then v is the LCA of u and v if u = = v: return v # Finding the node closest to the # root which is not the common ancestor # of u and v i.e. a node x such that x # is not the common ancestor of u # and v but memo[x][0] is for i in range (log, - 1 , - 1 ): if memo[u][i] ! = memo[v][i]: u = memo[u][i] v = memo[v][i] # Returning the first ancestor # of above found node return memo[u][ 0 ] # Driver code # Number of nodes n = 9 log = math.ceil(math.log(n, 2 )) g = [[] for i in range (n + 1 )] memo = [[ - 1 for i in range (log + 1 )] for j in range (n + 1 )] # Stores the level of each node lev = [ 0 for i in range (n + 1 )] # Add edges g[ 1 ].append( 2 ) g[ 2 ].append( 1 ) g[ 1 ].append( 3 ) g[ 3 ].append( 1 ) g[ 1 ].append( 4 ) g[ 4 ].append( 1 ) g[ 2 ].append( 5 ) g[ 5 ].append( 2 ) g[ 3 ].append( 6 ) g[ 6 ].append( 3 ) g[ 3 ].append( 7 ) g[ 7 ].append( 3 ) g[ 3 ].append( 8 ) g[ 8 ].append( 3 ) g[ 4 ].append( 9 ) g[ 9 ].append( 4 ) dfs( 1 , 1 , memo, lev, log, g) print ( "The LCA of 6 and 9 is" , lca( 6 , 9 , log, lev, memo)) print ( "The LCA of 5 and 9 is" , lca( 5 , 9 , log, lev, memo)) print ( "The LCA of 6 and 8 is" , lca( 6 , 8 , log, lev, memo)) print ( "The LCA of 6 and 1 is" , lca( 6 , 1 , log, lev, memo)) # This code is contributed by Bhaskar |
C#
// C# implementation of the approach using System; using System.Collections.Generic; class GFG { // List to store tree static List< int > []g; static int [,]memo; static int []lev; static int log; // Pre-processing to calculate // values of memo[,] static void dfs( int u, int p) { // Using recursion formula to // calculate the values of memo[,] memo[u, 0] = p; for ( int i = 1; i <= log; i++) memo[u, i] = memo[memo[u, i - 1], i - 1]; foreach ( int v in g[u]) { if (v != p) { // Calculating the level of each node lev[v] = lev[u] + 1; dfs(v, u); } } } // Function to return the LCA of // nodes u and v static int lca( int u, int v) { // The node which is present farthest // from the root node is taken as u // If v is farther from root node // then swap the two if (lev[u] < lev[v]) { int temp = u; u = v; v = temp; } // Finding the ancestor of u // which is at same level as v for ( int i = log; i >= 0; i--) { if ((lev[u] - ( int )Math.Pow(2, i)) >= lev[v]) u = memo[u, i]; } // If v is the ancestor of u // then v is the LCA of u and v if (u == v) return u; // Finding the node closest to the root // which is not the common ancestor of // u and v i.e. a node x such that // x is not the common ancestor of u // and v but memo[x,0] is for ( int i = log; i >= 0; i--) { if (memo[u, i] != memo[v, i]) { u = memo[u, i]; v = memo[v, i]; } } // Returning the first ancestor // of above found node return memo[u, 0]; } // Driver code public static void Main(String []args) { // Number of nodes int n = 9; g = new List< int >[n + 1]; // log(n) with base 2 log = ( int )Math.Ceiling(Math.Log(n) / Math.Log(2)); memo = new int [n + 1, log + 1]; // Stores the level of each node lev = new int [n + 1]; // Initialising memo values with -1 for ( int i = 0; i <= n; i++) for ( int j = 0; j <= log; j++) memo[i, j] = -1; for ( int i = 0; i <= n; i++) g[i] = new List< int >(); // Add edges g[1].Add(2); g[2].Add(1); g[1].Add(3); g[3].Add(1); g[1].Add(4); g[4].Add(1); g[2].Add(5); g[5].Add(2); g[3].Add(6); g[6].Add(3); g[3].Add(7); g[7].Add(3); g[3].Add(8); g[8].Add(3); g[4].Add(9); g[9].Add(4); dfs(1, 1); Console.WriteLine( "The LCA of 6 and 9 is " + lca(6, 9)); Console.WriteLine( "The LCA of 5 and 9 is " + lca(5, 9)); Console.WriteLine( "The LCA of 6 and 8 is " + lca(6, 8)); Console.WriteLine( "The LCA of 6 and 1 is " + lca(6, 1)); } } // This code is contributed by PrinciRaj1992 |
Javascript
<script> // JavaScript implementation of the approach // ArrayList to store tree let g; let memo, lev, log; // Pre-processing to calculate values of memo[][] function dfs(u, p) { // Using recursion formula to calculate // the values of memo[][] memo[u][0] = p; for (let i = 1; i <= log; i++) memo[u][i] = memo[memo[u][i - 1]][i - 1]; for (let v = 0; v < g[u].length; v++) { if (g[u][v] != p) { // Calculating the level of each node lev[g[u][v]] = lev[u] + 1; dfs(g[u][v], u); } } } // Function to return the LCA of nodes u and v function lca(u, v) { // The node which is present farthest // from the root node is taken as u // If v is farther from root node // then swap the two if (lev[u] < lev[v]) { let temp = u; u = v; v = temp; } // Finding the ancestor of u // which is at same level as v for (let i = log; i >= 0; i--) { if ((lev[u] - Math.pow(2, i)) >= lev[v]) u = memo[u][i]; } // If v is the ancestor of u // then v is the LCA of u and v if (u == v) return u; // Finding the node closest to the root which is // not the common ancestor of u and v i.e. a node // x such that x is not the common ancestor of u // and v but memo[x][0] is for (let i = log; i >= 0; i--) { if (memo[u][i] != memo[v][i]) { u = memo[u][i]; v = memo[v][i]; } } // Returning the first ancestor // of above found node return memo[u][0]; } // Number of nodes let n = 9; g = new Array(n + 1); // log(n) with base 2 log = Math.ceil(Math.log(n) / Math.log(2)); memo = new Array(n + 1); // Stores the level of each node lev = new Array(n + 1); lev.fill(0); // Initialising memo values with -1 for (let i = 0; i <= n; i++) { memo[i] = new Array(log+1); for (let j = 0; j < log+1; j++) { memo[i][j] = -1; } } for (let i = 0; i <= n; i++) g[i] = []; // Add edges g[1].push(2); g[2].push(1); g[1].push(3); g[3].push(1); g[1].push(4); g[4].push(1); g[2].push(5); g[5].push(2); g[3].push(6); g[6].push(3); g[3].push(7); g[7].push(3); g[3].push(8); g[8].push(3); g[4].push(9); g[9].push(4); dfs(1, 1); document.write( "The LCA of 6 and 9 is " + lca(6, 9) + "</br>" ); document.write( "The LCA of 5 and 9 is " + lca(5, 9) + "</br>" ); document.write( "The LCA of 6 and 8 is " + lca(6, 8) + "</br>" ); document.write( "The LCA of 6 and 1 is " + lca(6, 1)); </script> |
The LCA of 6 and 9 is 1 The LCA of 5 and 9 is 1 The LCA of 6 and 8 is 3 The LCA of 6 and 1 is 1
Time Complexity: The time taken in pre-processing is O(NlogN) and every query takes O(logN) time. So the overall time complexity of the solution is O(NlogN).
Auxiliary Space: O(NlogN)
Please Login to comment...