我们已经看到了各种具有不同时间复杂度的方法来计算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
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.
第一步是将树分解为平面线性数组。为此,我们可以应用Euler步行。欧拉游走将给出图的预遍历。因此,我们将在树上执行欧拉遍历,并在访问节点时将其存储在数组中。此过程将树的数据结构简化为简单的线性数组。
考虑下面的那棵树,而欧拉就越过它:-
现在,让我们大致考虑一下:考虑树上的任何两个节点。将只有一条路径连接两个节点,并且路径中深度值最小的节点将是两个给定节点的LCA。
现在,在Euler Walk数组中取任意两个不同的节点,例如u和v 。现在,从u到v的路径中的所有元素都将位于Euler walk数组中的u和v节点的索引之间。因此,我们只需要计算在Euler数组中节点u和节点v的索引之间的深度最小的节点即可。
为此,我们将维护另一个数组,其中包含所有节点的深度(与它们在欧拉步行数组中的位置相对应),以便可以在其上应用我们的RMQ算法。
下面给出的是与深度轨道阵列平行的欧拉步行阵列。
示例:-考虑欧拉阵列中的两个节点,节点6和节点7 。为了计算节点6和节点7的LCA,我们寻找节点6和节点7之间的所有节点的最小深度值。
因此,节点1的最小深度值= 0 ,因此,它是节点6和节点7的LCA。
执行:-
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) 。