先决条件:简介和DFS
任务是在树(不一定是二叉树)中找到两个给定节点的LCA。在以前的文章中,我们已经看到了如何使用稀疏矩阵DP方法来计算LCA。在本文中,我们将看到通过sqrt分解技术对Naive方法进行的优化,该优化效果优于Naive方法。
天真的方法
为了首先计算两个节点的LCA,我们将使两个节点的深度相同,方法是使深度较大的节点在树上向上移动一个父节点,直到两个节点的高度相同。一旦两个节点都处于相同的高度,我们就可以同时开始为两个节点同时跳一个父节点,直到两个节点变得相等,并且该节点将成为两个最初给定节点的LCA。
考虑下面的深度为9的n元树,让我们检查一下该样本树的幼稚方法是如何工作的。
在上面的树中,我们需要计算节点6和节点30的LCA
显然,节点30的深度大于节点6的深度。因此,首先,我们开始在节点30上方跳一个父级,直到达到节点6的深度值,即深度2。
上图中的橙色路径说明了到达深度2的跳转顺序。在此过程中,我们仅将一个父级跳转到当前节点上方。
现在两个节点的深度为2。因此,现在两个节点将向上跳一个父节点,直到两个节点相等。这两个节点首次变为相等的末端节点就是我们的LCA。
上图中的蓝色路径显示了两个节点的跳跃路线
以上实现的代码:
C++
// Naive C++ implementation to find LCA in a tree
#include
using namespace std;
#define MAXN 1001
int depth[MAXN]; // stores depth for each node
int parent[MAXN]; // stores first parent for each node
vector < int > adj[MAXN];
void addEdge(int u,int v)
{
adj[u].push_back(v);
adj[v].push_back(u);
}
void dfs(int cur, int prev)
{
// marking parent for each node
parent[cur] = prev;
// marking depth for each node
depth[cur] = depth[prev] + 1;
// propogating marking down the tree
for (int i=0; i depth[v])
swap(u, v);
v = parent[v];
return LCANaive(u,v);
}
// Driver function to call the above functions
int main(int argc, char const *argv[])
{
// adding edges to the tree
addEdge(1,2);
addEdge(1,3);
addEdge(1,4);
addEdge(2,5);
addEdge(2,6);
addEdge(3,7);
addEdge(4,8);
addEdge(4,9);
addEdge(9,10);
addEdge(9,11);
addEdge(7,12);
addEdge(7,13);
preprocess();
cout << "LCA(11,8) : " << LCANaive(11,8) << endl;
cout << "LCA(3,13) : " << LCANaive(3,13) << endl;
return 0;
}
Java
// Naive Java implementation to find LCA in a tree.
import java.io.*;
import java.util.*;
class GFG
{
static int MAXN = 1001;
// stores depth for each node
static int[] depth = new int[MAXN];
// stores first parent for each node
static int[] parent = new int[MAXN];
@SuppressWarnings("unchecked")
static Vector[] adj = new Vector[MAXN];
static
{
for (int i = 0; i < MAXN; i++)
adj[i] = new Vector<>();
}
static void addEdge(int u, int v)
{
adj[u].add(v);
adj[v].add(u);
}
static void dfs(int cur, int prev)
{
// marking parent for each node
parent[cur] = prev;
// marking depth for each node
depth[cur] = depth[prev] + 1;
// propogating marking down the tree
for (int i = 0; i < adj[cur].size(); i++)
if (adj[cur].elementAt(i) != prev)
dfs(adj[cur].elementAt(i), cur);
}
static void preprocess()
{
// a dummy node
depth[0] = -1;
// precalclating 1)depth. 2)parent.
// for each node
dfs(1, 0);
}
// Time Complexity : O(Height of tree)
// recursively jumps one node above
// till both the nodes become equal
static int LCANaive(int u, int v)
{
if (u == v)
return u;
if (depth[u] > depth[v])
{
int temp = u;
u = v;
v = temp;
}
v = parent[v];
return LCANaive(u, v);
}
// Driver Code
public static void main(String[] args)
{
// adding edges to the tree
addEdge(1, 2);
addEdge(1, 3);
addEdge(1, 4);
addEdge(2, 5);
addEdge(2, 6);
addEdge(3, 7);
addEdge(4, 8);
addEdge(4, 9);
addEdge(9, 10);
addEdge(9, 11);
addEdge(7, 12);
addEdge(7, 13);
preprocess();
System.out.println("LCA(11,8) : " + LCANaive(11, 8));
System.out.println("LCA(3,13) : " + LCANaive(3, 13));
}
}
// This code is contributed by
// sanjeev2552
Python3
# Python3 implementation to
# find LCA in a tree
MAXN = 1001
# stores depth for each node
depth = [0 for i in range(MAXN)];
# stores first parent for each node
parent = [0 for i in range(MAXN)];
adj = [[] for i in range(MAXN)]
def addEdge(u, v):
adj[u].append(v);
adj[v].append(u);
def dfs(cur, prev):
# marking parent for
# each node
parent[cur] = prev;
# marking depth for
# each node
depth[cur] = depth[prev] + 1;
# propogating marking down
# the tree
for i in range(len(adj[cur])):
if (adj[cur][i] != prev):
dfs(adj[cur][i], cur);
def preprocess():
# a dummy node
depth[0] = -1;
# precalculating 1)depth.
# 2)parent. for each node
dfs(1, 0);
# Time Complexity : O(Height of tree)
# recursively jumps one node above
# till both the nodes become equal
def LCANaive(u, v):
if (u == v):
return u;
if (depth[u] > depth[v]):
u, v = v, u
v = parent[v];
return LCANaive(u, v);
# Driver code
if __name__ == "__main__":
# adding edges to the tree
addEdge(1, 2);
addEdge(1, 3);
addEdge(1, 4);
addEdge(2, 5);
addEdge(2, 6);
addEdge(3, 7);
addEdge(4, 8);
addEdge(4, 9);
addEdge(9, 10);
addEdge(9, 11);
addEdge(7, 12);
addEdge(7, 13);
preprocess();
print('LCA(11,8) : ' +
str(LCANaive(11, 8)))
print('LCA(3,13) : ' +
str(LCANaive(3, 13)))
# This code is contributed by RUtvik_56
C#
// Naive C# implementation to find
// LCA in a tree.
using System;
using System.Collections;
class GFG{
static int MAXN = 1001;
// Stores depth for each node
static int[] depth = new int[MAXN];
// Stores first parent for each node
static int[] parent = new int[MAXN];
static ArrayList[] adj = new ArrayList[MAXN];
static void addEdge(int u, int v)
{
adj[u].Add(v);
adj[v].Add(u);
}
static void dfs(int cur, int prev)
{
// Marking parent for each node
parent[cur] = prev;
// Marking depth for each node
depth[cur] = depth[prev] + 1;
// Propogating marking down the tree
for(int i = 0; i < adj[cur].Count; i++)
if ((int)adj[cur][i] != prev)
dfs((int)adj[cur][i], cur);
}
static void preprocess()
{
// A dummy node
depth[0] = -1;
// Precalclating 1)depth. 2)parent.
// for each node
dfs(1, 0);
}
// Time Complexity : O(Height of tree)
// recursively jumps one node above
// till both the nodes become equal
static int LCANaive(int u, int v)
{
if (u == v)
return u;
if (depth[u] > depth[v])
{
int temp = u;
u = v;
v = temp;
}
v = parent[v];
return LCANaive(u, v);
}
// Driver Code
public static void Main(string[] args)
{
for(int i = 0; i < MAXN; i++)
adj[i] = new ArrayList();
// Adding edges to the tree
addEdge(1, 2);
addEdge(1, 3);
addEdge(1, 4);
addEdge(2, 5);
addEdge(2, 6);
addEdge(3, 7);
addEdge(4, 8);
addEdge(4, 9);
addEdge(9, 10);
addEdge(9, 11);
addEdge(7, 12);
addEdge(7, 13);
preprocess();
Console.WriteLine("LCA(11, 8) : " +
LCANaive(11, 8));
Console.WriteLine("LCA(3, 13) : " +
LCANaive(3, 13));
}
}
// This code is contributed by pratham76
C++
// C++ program to find LCA using Sqrt decomposition
#include "iostream"
#include "vector"
#include "math.h"
using namespace std;
#define MAXN 1001
int block_sz; // block size = sqrt(height)
int depth[MAXN]; // stores depth for each node
int parent[MAXN]; // stores first parent for
// each node
int jump_parent[MAXN]; // stores first ancestor in
// previous block
vector < int > adj[MAXN];
void addEdge(int u,int v)
{
adj[u].push_back(v);
adj[v].push_back(u);
}
int LCANaive(int u,int v)
{
if (u == v) return u;
if (depth[u] > depth[v])
swap(u,v);
v = parent[v];
return LCANaive(u,v);
}
// precalculating the required parameters
// associated with every node
void dfs(int cur, int prev)
{
// marking depth of cur node
depth[cur] = depth[prev] + 1;
// marking parent of cur node
parent[cur] = prev;
// making jump_parent of cur node
if (depth[cur] % block_sz == 0)
/* if it is first node of the block
then its jump_parent is its cur parent */
jump_parent[cur] = parent[cur];
else
/* if it is not the first node of this block
then its jump_parent is jump_parent of
its parent */
jump_parent[cur] = jump_parent[prev];
// propogating the marking down the subtree
for (int i = 0; i depth[v])
// maintaining depth[v] > depth[u]
swap(u,v);
// climb to its jump parent
v = jump_parent[v];
}
// u and v have same jump_parent
return LCANaive(u,v);
}
void preprocess(int height)
{
block_sz = sqrt(height);
depth[0] = -1;
// precalclating 1)depth. 2)parent. 3)jump_parent
// for each node
dfs(1, 0);
}
// Driver function to call the above functions
int main(int argc, char const *argv[])
{
// adding edges to the tree
addEdge(1,2);
addEdge(1,3);
addEdge(1,4);
addEdge(2,5);
addEdge(2,6);
addEdge(3,7);
addEdge(4,8);
addEdge(4,9);
addEdge(9,10);
addEdge(9,11);
addEdge(7,12);
addEdge(7,13);
// here we are directly taking height = 4
// according to the given tree but we can
// pre-calculate height = max depth
// in one more dfs
int height = 4;
preprocess(height);
cout << "LCA(11,8) : " << LCASQRT(11,8) << endl;
cout << "LCA(3,13) : " << LCASQRT(3,13) << endl;
return 0;
}
Java
// Java program to find LCA using Sqrt decomposition
import java.util.*;
class GFG
{
static final int MAXN = 1001;
static int block_sz; // block size = Math.sqrt(height)
static int []depth = new int[MAXN]; // stores depth for each node
static int []parent = new int[MAXN]; // stores first parent for
// each node
static int []jump_parent = new int[MAXN]; // stores first ancestor in
// previous block
static Vector []adj = new Vector[MAXN];
static void addEdge(int u,int v)
{
adj[u].add(v);
adj[v].add(u);
}
static int LCANaive(int u,int v)
{
if (u == v) return u;
if (depth[u] > depth[v])
{
int t = u;
u = v;
v = t;
}
v = parent[v];
return LCANaive(u, v);
}
// precalculating the required parameters
// associated with every node
static void dfs(int cur, int prev)
{
// marking depth of cur node
depth[cur] = depth[prev] + 1;
// marking parent of cur node
parent[cur] = prev;
// making jump_parent of cur node
if (depth[cur] % block_sz == 0)
/* if it is first node of the block
then its jump_parent is its cur parent */
jump_parent[cur] = parent[cur];
else
/* if it is not the first node of this block
then its jump_parent is jump_parent of
its parent */
jump_parent[cur] = jump_parent[prev];
// propogating the marking down the subtree
for (int i = 0; i < adj[cur].size(); ++i)
if (adj[cur].get(i) != prev)
dfs(adj[cur].get(i), cur);
}
// using sqrt decomposition trick
static int LCASQRT(int u, int v)
{
while (jump_parent[u] != jump_parent[v])
{
if (depth[u] > depth[v])
{
// maintaining depth[v] > depth[u]
int t = u;
u = v;
v = t;
}
// climb to its jump parent
v = jump_parent[v];
}
// u and v have same jump_parent
return LCANaive(u, v);
}
static void preprocess(int height)
{
block_sz = (int)Math.sqrt(height);
depth[0] = -1;
// precalclating 1)depth. 2)parent. 3)jump_parent
// for each node
dfs(1, 0);
}
// Driver code
public static void main(String []args)
{
for (int i = 0; i < adj.length; i++)
adj[i] = new Vector();
// adding edges to the tree
addEdge(1, 2);
addEdge(1, 3);
addEdge(1, 4);
addEdge(2, 5);
addEdge(2, 6);
addEdge(3, 7);
addEdge(4, 8);
addEdge(4, 9);
addEdge(9, 10);
addEdge(9, 11);
addEdge(7, 12);
addEdge(7, 13);
// here we are directly taking height = 4
// according to the given tree but we can
// pre-calculate height = max depth
// in one more dfs
int height = 4;
preprocess(height);
System.out.print("LCA(11,8) : " + LCASQRT(11, 8) +"\n");
System.out.print("LCA(3,13) : " + LCASQRT(3, 13) +"\n");
}
}
// This code is contributed by aashish1995.
C#
// C# program to find LCA using Sqrt decomposition
using System;
using System.Collections.Generic;
public class GFG
{
static readonly int MAXN = 1001;
static int block_sz; // block size = Math.Sqrt(height)
static int []depth = new int[MAXN]; // stores depth for each node
static int []parent = new int[MAXN]; // stores first parent for
// each node
static int []jump_parent = new int[MAXN]; // stores first ancestor in
// previous block
static List []adj = new List[MAXN];
static void addEdge(int u, int v)
{
adj[u].Add(v);
adj[v].Add(u);
}
static int LCANaive(int u, int v)
{
if (u == v) return u;
if (depth[u] > depth[v])
{
int t = u;
u = v;
v = t;
}
v = parent[v];
return LCANaive(u, v);
}
// precalculating the required parameters
// associated with every node
static void dfs(int cur, int prev)
{
// marking depth of cur node
depth[cur] = depth[prev] + 1;
// marking parent of cur node
parent[cur] = prev;
// making jump_parent of cur node
if (depth[cur] % block_sz == 0)
/* if it is first node of the block
then its jump_parent is its cur parent */
jump_parent[cur] = parent[cur];
else
/* if it is not the first node of this block
then its jump_parent is jump_parent of
its parent */
jump_parent[cur] = jump_parent[prev];
// propogating the marking down the subtree
for (int i = 0; i < adj[cur].Count; ++i)
if (adj[cur][i] != prev)
dfs(adj[cur][i], cur);
}
// using sqrt decomposition trick
static int LCASQRT(int u, int v)
{
while (jump_parent[u] != jump_parent[v])
{
if (depth[u] > depth[v])
{
// maintaining depth[v] > depth[u]
int t = u;
u = v;
v = t;
}
// climb to its jump parent
v = jump_parent[v];
}
// u and v have same jump_parent
return LCANaive(u, v);
}
static void preprocess(int height)
{
block_sz = (int)Math.Sqrt(height);
depth[0] = -1;
// precalclating 1)depth. 2)parent. 3)jump_parent
// for each node
dfs(1, 0);
}
// Driver code
public static void Main(String []args)
{
for (int i = 0; i < adj.Length; i++)
adj[i] = new List();
// adding edges to the tree
addEdge(1, 2);
addEdge(1, 3);
addEdge(1, 4);
addEdge(2, 5);
addEdge(2, 6);
addEdge(3, 7);
addEdge(4, 8);
addEdge(4, 9);
addEdge(9, 10);
addEdge(9, 11);
addEdge(7, 12);
addEdge(7, 13);
// here we are directly taking height = 4
// according to the given tree but we can
// pre-calculate height = max depth
// in one more dfs
int height = 4;
preprocess(height);
Console.Write("LCA(11,8) : " + LCASQRT(11, 8) +"\n");
Console.Write("LCA(3,13) : " + LCASQRT(3, 13) +"\n");
}
}
// This code is contributed by Rajput-Ji
LCA(11,8) : 4
LCA(3,13) : 3
时间复杂度:我们使用O(n)中的一个DFS遍历来预先计算每个节点的深度。现在,在最坏的情况下,两个节点将成为根节点不同子分支中树上两个最底层的节点。因此,在这种情况下,根将是两个节点的LCA。因此,两个节点都必须恰好跳到高度h以上,其中h是树的高度。因此,要回答每个LCA查询,时间复杂度将为O(h) 。
Sqrt分解技巧:
我们根据树的深度将其分为不同的组。假设树的深度h是一个完美的平方。因此,再次像普通的sqrt分解方法一样,我们将拥有sqrt(h)块或组。从深度0到深度sqrt(h)– 1的节点位于第一组;然后,深度为sqrt(H)至2 * sqrt(h)-1的节点位于第二组,依此类推,直到最后一个节点。
我们跟踪每个节点的对应组号以及每个节点的深度。这可以通过树上的单个dfs来完成(请参阅代码以更好地理解)。
Sqrt技巧:-在幼稚的方法中,我们将一个父级跳到树上,直到两个节点的深度不同。但是在这里,我们执行逐组跳转。要执行此逐组跳转,我们需要与每个节点相关联的两个参数:1)父级和2)跳转父级
此处,每个节点的父级定义为直接与其相连的当前节点上方的第一个节点,而每个节点的jump_parent是该节点中当前节点上方组中当前节点的第一个祖先的节点。
因此,现在我们需要为每个节点维护3个参数:
1)深度
2)父母
3)jump_parent
所有这三个参数都可以在一个dfs中维护(请参阅代码以更好地理解)
优化过程的伪代码
LCAsqrt(u, v){
// assuming v is at greater depth
while (jump_parent[u]!=jump_parent[v]){
v = jump_parent[v];
}
// now both nodes are in same group
// and have same jump_parent
return LCAnaive(u,v);
}
这里的关键概念是,首先通过将分解后的块一个接一个地爬到树上,将两个节点都放在同一组中,并且具有相同的jump_parent,然后当两个节点在同一组中并且具有相同的jump_parent时,我们使用幼稚的方法来查找节点的LCA。
这种优化的组跳跃技术将迭代空间减少了sqrt(h) ,从而降低了时间复杂度(有关更好的时间复杂度分析,请参见下文)
让我们将上面的树分解为sqrt(h)组(h = 9),并计算节点6和30的LCA。
在上面分解的树中
Jump_parent[6] = 0 parent[6] = 3
Jump_parent[5] = 0 parent[5] = 2
Jump_parent[1] = 0 parent[1] = 0
Jump_parent[11] = 6 parent[11] = 6
Jump_parent[15] = 6 parent[15] = 11
Jump_parent[21] = 6 parent[21] = 15
Jump_parent[25] = 21 parent[25] = 21
Jump_parent[26] = 21 parent[26] = 21
Jump_parent[30] = 21 parent[30] = 25
现在在此阶段,节点30的Jump_parent为21,节点5的Jump_parent为0,因此,我们将绑定到jump_parent [30]即节点21
现在,节点21的Jump_parent再次不等于节点5的Jump_parent。因此,我们将再次爬到jump_parent [21],即节点6
在此阶段jump_parent [6] == jump_parent [5],所以现在我们将使用朴素的爬升方法,在两个节点上爬一个父级,直到到达节点1,这将是必需的LCA。
上图中的蓝色路径描述了节点6和节点5的跳跃路径序列。
上面描述的C++代码如下:
C++
// C++ program to find LCA using Sqrt decomposition
#include "iostream"
#include "vector"
#include "math.h"
using namespace std;
#define MAXN 1001
int block_sz; // block size = sqrt(height)
int depth[MAXN]; // stores depth for each node
int parent[MAXN]; // stores first parent for
// each node
int jump_parent[MAXN]; // stores first ancestor in
// previous block
vector < int > adj[MAXN];
void addEdge(int u,int v)
{
adj[u].push_back(v);
adj[v].push_back(u);
}
int LCANaive(int u,int v)
{
if (u == v) return u;
if (depth[u] > depth[v])
swap(u,v);
v = parent[v];
return LCANaive(u,v);
}
// precalculating the required parameters
// associated with every node
void dfs(int cur, int prev)
{
// marking depth of cur node
depth[cur] = depth[prev] + 1;
// marking parent of cur node
parent[cur] = prev;
// making jump_parent of cur node
if (depth[cur] % block_sz == 0)
/* if it is first node of the block
then its jump_parent is its cur parent */
jump_parent[cur] = parent[cur];
else
/* if it is not the first node of this block
then its jump_parent is jump_parent of
its parent */
jump_parent[cur] = jump_parent[prev];
// propogating the marking down the subtree
for (int i = 0; i depth[v])
// maintaining depth[v] > depth[u]
swap(u,v);
// climb to its jump parent
v = jump_parent[v];
}
// u and v have same jump_parent
return LCANaive(u,v);
}
void preprocess(int height)
{
block_sz = sqrt(height);
depth[0] = -1;
// precalclating 1)depth. 2)parent. 3)jump_parent
// for each node
dfs(1, 0);
}
// Driver function to call the above functions
int main(int argc, char const *argv[])
{
// adding edges to the tree
addEdge(1,2);
addEdge(1,3);
addEdge(1,4);
addEdge(2,5);
addEdge(2,6);
addEdge(3,7);
addEdge(4,8);
addEdge(4,9);
addEdge(9,10);
addEdge(9,11);
addEdge(7,12);
addEdge(7,13);
// here we are directly taking height = 4
// according to the given tree but we can
// pre-calculate height = max depth
// in one more dfs
int height = 4;
preprocess(height);
cout << "LCA(11,8) : " << LCASQRT(11,8) << endl;
cout << "LCA(3,13) : " << LCASQRT(3,13) << endl;
return 0;
}
Java
// Java program to find LCA using Sqrt decomposition
import java.util.*;
class GFG
{
static final int MAXN = 1001;
static int block_sz; // block size = Math.sqrt(height)
static int []depth = new int[MAXN]; // stores depth for each node
static int []parent = new int[MAXN]; // stores first parent for
// each node
static int []jump_parent = new int[MAXN]; // stores first ancestor in
// previous block
static Vector []adj = new Vector[MAXN];
static void addEdge(int u,int v)
{
adj[u].add(v);
adj[v].add(u);
}
static int LCANaive(int u,int v)
{
if (u == v) return u;
if (depth[u] > depth[v])
{
int t = u;
u = v;
v = t;
}
v = parent[v];
return LCANaive(u, v);
}
// precalculating the required parameters
// associated with every node
static void dfs(int cur, int prev)
{
// marking depth of cur node
depth[cur] = depth[prev] + 1;
// marking parent of cur node
parent[cur] = prev;
// making jump_parent of cur node
if (depth[cur] % block_sz == 0)
/* if it is first node of the block
then its jump_parent is its cur parent */
jump_parent[cur] = parent[cur];
else
/* if it is not the first node of this block
then its jump_parent is jump_parent of
its parent */
jump_parent[cur] = jump_parent[prev];
// propogating the marking down the subtree
for (int i = 0; i < adj[cur].size(); ++i)
if (adj[cur].get(i) != prev)
dfs(adj[cur].get(i), cur);
}
// using sqrt decomposition trick
static int LCASQRT(int u, int v)
{
while (jump_parent[u] != jump_parent[v])
{
if (depth[u] > depth[v])
{
// maintaining depth[v] > depth[u]
int t = u;
u = v;
v = t;
}
// climb to its jump parent
v = jump_parent[v];
}
// u and v have same jump_parent
return LCANaive(u, v);
}
static void preprocess(int height)
{
block_sz = (int)Math.sqrt(height);
depth[0] = -1;
// precalclating 1)depth. 2)parent. 3)jump_parent
// for each node
dfs(1, 0);
}
// Driver code
public static void main(String []args)
{
for (int i = 0; i < adj.length; i++)
adj[i] = new Vector();
// adding edges to the tree
addEdge(1, 2);
addEdge(1, 3);
addEdge(1, 4);
addEdge(2, 5);
addEdge(2, 6);
addEdge(3, 7);
addEdge(4, 8);
addEdge(4, 9);
addEdge(9, 10);
addEdge(9, 11);
addEdge(7, 12);
addEdge(7, 13);
// here we are directly taking height = 4
// according to the given tree but we can
// pre-calculate height = max depth
// in one more dfs
int height = 4;
preprocess(height);
System.out.print("LCA(11,8) : " + LCASQRT(11, 8) +"\n");
System.out.print("LCA(3,13) : " + LCASQRT(3, 13) +"\n");
}
}
// This code is contributed by aashish1995.
C#
// C# program to find LCA using Sqrt decomposition
using System;
using System.Collections.Generic;
public class GFG
{
static readonly int MAXN = 1001;
static int block_sz; // block size = Math.Sqrt(height)
static int []depth = new int[MAXN]; // stores depth for each node
static int []parent = new int[MAXN]; // stores first parent for
// each node
static int []jump_parent = new int[MAXN]; // stores first ancestor in
// previous block
static List []adj = new List[MAXN];
static void addEdge(int u, int v)
{
adj[u].Add(v);
adj[v].Add(u);
}
static int LCANaive(int u, int v)
{
if (u == v) return u;
if (depth[u] > depth[v])
{
int t = u;
u = v;
v = t;
}
v = parent[v];
return LCANaive(u, v);
}
// precalculating the required parameters
// associated with every node
static void dfs(int cur, int prev)
{
// marking depth of cur node
depth[cur] = depth[prev] + 1;
// marking parent of cur node
parent[cur] = prev;
// making jump_parent of cur node
if (depth[cur] % block_sz == 0)
/* if it is first node of the block
then its jump_parent is its cur parent */
jump_parent[cur] = parent[cur];
else
/* if it is not the first node of this block
then its jump_parent is jump_parent of
its parent */
jump_parent[cur] = jump_parent[prev];
// propogating the marking down the subtree
for (int i = 0; i < adj[cur].Count; ++i)
if (adj[cur][i] != prev)
dfs(adj[cur][i], cur);
}
// using sqrt decomposition trick
static int LCASQRT(int u, int v)
{
while (jump_parent[u] != jump_parent[v])
{
if (depth[u] > depth[v])
{
// maintaining depth[v] > depth[u]
int t = u;
u = v;
v = t;
}
// climb to its jump parent
v = jump_parent[v];
}
// u and v have same jump_parent
return LCANaive(u, v);
}
static void preprocess(int height)
{
block_sz = (int)Math.Sqrt(height);
depth[0] = -1;
// precalclating 1)depth. 2)parent. 3)jump_parent
// for each node
dfs(1, 0);
}
// Driver code
public static void Main(String []args)
{
for (int i = 0; i < adj.Length; i++)
adj[i] = new List();
// adding edges to the tree
addEdge(1, 2);
addEdge(1, 3);
addEdge(1, 4);
addEdge(2, 5);
addEdge(2, 6);
addEdge(3, 7);
addEdge(4, 8);
addEdge(4, 9);
addEdge(9, 10);
addEdge(9, 11);
addEdge(7, 12);
addEdge(7, 13);
// here we are directly taking height = 4
// according to the given tree but we can
// pre-calculate height = max depth
// in one more dfs
int height = 4;
preprocess(height);
Console.Write("LCA(11,8) : " + LCASQRT(11, 8) +"\n");
Console.Write("LCA(3,13) : " + LCASQRT(3, 13) +"\n");
}
}
// This code is contributed by Rajput-Ji
输出:
LCA(11,8) : 4
LCA(3,13) : 3
注意:即使高度不是完美的正方形,上述代码也可以使用。
现在,让我们看看如何通过这种简单的分组技术来改变时间复杂度:
时间复杂度分析:
我们根据树的深度将树分为sqrt(h)组,每组包含深度差等于sqrt(h)的节点。现在再次以最坏情况为例,假设第一个节点’u’在第一个组中,而节点’v’在第sqrt(h)个组(最后一个组)中。因此,首先我们要进行组跳跃(单组跳跃),直到从最后一组进入第1组为止。这将完全花费sqrt(h)– 1次迭代或跳转。因此,直到这一步时间复杂度为O(sqrt(h)) 。
现在,当我们处于同一组中时,我们将调用LCAnaive函数。 LCA_Naive的时间复杂度为O(sqrt(h’)),其中h’是树的高度。现在,在我们的情况下,h’的值将为sqrt(h),因为每个组的子树的最大sqrt(h)高度为。因此,此步骤的复杂度也为O(sqrt(h))。
因此,总的时间复杂度将为O(sqrt(h)+ sqrt(h))〜O(sqrt(h)) 。