📜  Sqrt(或平方根)分解|集合2(O(sqrt(height))时间中树的LCA)

📅  最后修改于: 2021-06-27 00:47:58             🧑  作者: Mango

先决条件:简介和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。

16492369_1290074911076976_1745511031_o

在上面分解的树中

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))

如果您希望与行业专家一起参加现场课程,请参阅《 Geeks现场课程》和《 Geeks现场课程美国》。