📜  Tarjan的离线最低共同祖先算法

📅  最后修改于: 2021-04-17 10:10:12             🧑  作者: Mango

先决条件:LCA基础知识,按等级和路径压缩的不相交集并集
我们得到了一棵树(可以扩展为DAG),并且有许多形式为LCA(u,v)的查询,即找到节点’u’和’v’的LCA。
我们可以使用RMQO(N + QlogN)的时间内执行这些查询,其中O(N)用于预处理,O(log N)用于回答查询,其中
N =节点数,并且
Q =要回答的查询数。
我们可以做得更好吗?我们可以在线性(几乎)时间内做吗?是的。
本文介绍了一种离线算法,该算法可以在大约O(N + Q)的时间内执行这些查询。尽管这不是完全线性的,但时间复杂度分析中涉及逆阿克曼函数。有关逆阿克曼函数的更多详细信息,请参见此。综上所述,对于任何可以物理逆写的输入大小值,我们可以说逆阿克曼函数保持小于4。因此,我们认为这几乎是线性的。
我们考虑如下所示的输入树。我们将根据以下说明对树进行预处理并填充两个数组: child []和sibling []-

树1

让我们想处理这些查询-LCA (5,4),LCA(1,3),LCA(2,3)
现在,经过预处理后,我们从树的根(此处为节点“ 1”)开始执行LCA遍历。但是在LCA遍历之前,我们使用WHITE为所有节点着色。在整个LCA遍历过程中,我们使用三个不相交的集合联合函数-makeSet(),findSet(),unionSet()。
这些功能使用等级和路径压缩并集技术来改善运行时间。在LCA遍历期间,我们的查询将得到处理和输出(以随机顺序)。在整个树的LCA遍历之后,所有节点的颜色都变为BLACK
Tarjan离线LCA算法从CLRS,第21-3节,第584页,第二版/第三版开始。

tre22

注意:可能无法按原始顺序处理查询。我们可以轻松地修改流程并根据输入顺序对其进行排序。
下面的图片清楚地描述了所有正在发生的步骤。红色箭头显示了递归函数LCA()的行进方向。

树3

树4

树5

树6

从上面的图片中我们可以清楚地看到,查询按以下顺序处理:LCA(5,4),LCA(2,3),LCA(1,3),其输入顺序与输入顺序不同(LCA(5,4),LCA(1,3),LCA(2,3))。

下面是上述方法的实现:

C++
// A C++ Program to implement Tarjan Offline LCA Algorithm
#include 
 
#define V 5       // number of nodes in input tree
#define WHITE 1      // COLOUR 'WHITE' is assigned value 1
#define BLACK 2   // COLOUR 'BLACK' is assigned value 2
 
/* A binary tree node has data, pointer to left child
   and a pointer to right child */
struct Node
{
    int data;
    Node* left, *right;
};
 
/*
 subset[i].parent-->Holds the parent of node-'i'
 subset[i].rank-->Holds the rank of node-'i'
 subset[i].ancestor-->Holds the LCA queries answers
 subset[i].child-->Holds one of the child of node-'i'
                     if present, else -'0'
 subset[i].sibling-->Holds the right-sibling of node-'i'
                     if present, else -'0'
 subset[i].color-->Holds the colour of node-'i'
*/
struct subset
{
    int parent, rank, ancestor, child, sibling, color;
};
 
// Structure to represent a query
// A query consists of (L,R) and we will process the
// queries offline a/c to Tarjan's oflline LCA algorithm
struct Query
{
    int L, R;
};
 
/* Helper function that allocates a new node with the
   given data and NULL left and right pointers. */
Node* newNode(int data)
{
    Node* node = new Node;
    node->data = data;
    node->left = node->right = NULL;
    return(node);
}
 
//A utility function to make set
void makeSet(struct subset subsets[], int i)
{
    if (i < 1 || i > V)
        return;
 
    subsets[i].color = WHITE;
    subsets[i].parent = i;
    subsets[i].rank = 0;
 
    return;
}
 
// A utility function to find set of an element i
// (uses path compression technique)
int findSet(struct subset subsets[], int i)
{
    // find root and make root as parent of i (path compression)
    if (subsets[i].parent != i)
        subsets[i].parent = findSet (subsets, subsets[i].parent);
 
    return subsets[i].parent;
}
 
// A function that does union of two sets of x and y
// (uses union by rank)
void unionSet(struct subset subsets[], int x, int y)
{
    int xroot = findSet (subsets, x);
    int yroot = findSet (subsets, y);
 
    // Attach smaller rank tree under root of high rank tree
    // (Union by Rank)
    if (subsets[xroot].rank < subsets[yroot].rank)
        subsets[xroot].parent = yroot;
    else if (subsets[xroot].rank > subsets[yroot].rank)
        subsets[yroot].parent = xroot;
 
    // If ranks are same, then make one as root and increment
    // its rank by one
    else
    {
        subsets[yroot].parent = xroot;
        (subsets[xroot].rank)++;
    }
}
 
// The main function that prints LCAs. u is root's data.
// m is size of q[]
void lcaWalk(int u, struct Query q[], int m,
             struct subset subsets[])
{
    // Make Sets
    makeSet(subsets, u);
 
    // Initially, each node's ancestor is the node
    // itself.
    subsets[findSet(subsets, u)].ancestor = u;
 
    int child = subsets[u].child;
 
    // This while loop doesn't run for more than 2 times
    // as there can be at max. two children of a node
    while (child != 0)
    {
        lcaWalk(child, q, m, subsets);
        unionSet (subsets, u, child);
        subsets[findSet(subsets, u)].ancestor = u;
        child = subsets[child].sibling;
    }
 
    subsets[u].color = BLACK;
 
    for (int i = 0; i < m; i++)
    {
        if (q[i].L == u)
        {
            if (subsets[q[i].R].color == BLACK)
            {
                printf("LCA(%d %d) -> %d\n",
                  q[i].L,
                  q[i].R,
                  subsets[findSet(subsets,q[i].R)].ancestor);
            }
        }
        else if (q[i].R == u)
        {
            if (subsets[q[i].L].color == BLACK)
            {
                printf("LCA(%d %d) -> %d\n",
                  q[i].L,
                  q[i].R,
                  subsets[findSet(subsets,q[i].L)].ancestor);
            }
        }
    }
 
    return;
}
 
// This is basically an inorder traversal and
// we preprocess the arrays-> child[]
// and sibling[] in "struct subset" with
// the tree structure using this function.
void preprocess(Node * node, struct subset subsets[])
{
    if (node == NULL)
        return;
 
    // Recur on left child
    preprocess(node->left, subsets);
 
    if (node->left != NULL&&node->right != NULL)
    {
        /* Note that the below two lines can also be this-
        subsets[node->data].child = node->right->data;
        subsets[node->right->data].sibling =
                                         node->left->data;
 
        This is because if both left and right children of
        node-'i' are present then we can store any of them
        in subsets[i].child and correspondingly its sibling*/
        subsets[node->data].child = node->left->data;
        subsets[node->left->data].sibling =
            node->right->data;
 
    }
    else if ((node->left != NULL && node->right == NULL)
             || (node->left == NULL && node->right != NULL))
    {
        if(node->left != NULL && node->right == NULL)
            subsets[node->data].child = node->left->data;
        else
            subsets[node->data].child = node->right->data;
    }
 
    //Recur on right child
    preprocess (node->right, subsets);
}
 
// A function to initialise prior to pre-processing and
// LCA walk
void initialise(struct subset subsets[])
{
    // Initialising the structure with 0's
    memset(subsets, 0, (V+1) * sizeof(struct subset));
 
    // We colour all nodes WHITE before LCA Walk.
    for (int i=1; i<=V; i++)
        subsets[i].color=WHITE;
 
    return;
}
 
// Prints LCAs for given queries q[0..m-1] in a tree
// with given root
void printLCAs(Node *root, Query q[], int m)
{
    // Allocate memory for V subsets and nodes
    struct subset * subsets = new subset[V+1];
 
    // Creates subsets and colors them WHITE
    initialise(subsets);
 
    // Preprocess the tree
    preprocess(root, subsets);
 
    // Perform a tree walk to process the LCA queries
    // offline
    lcaWalk(root->data , q, m, subsets);
}
 
// Driver program to test above functions
int main()
{
    /*
     We construct a binary tree :-
            1
          /  \
         2    3
       /  \
      4    5           */
 
    Node *root = newNode(1);
    root->left        = newNode(2);
    root->right       = newNode(3);
    root->left->left  = newNode(4);
    root->left->right = newNode(5);
 
    // LCA Queries to answer
    Query q[] = {{5, 4}, {1, 3}, {2, 3}};
    int m = sizeof(q)/sizeof(q[0]);
 
    printLCAs(root, q, m);
 
    return 0;
}


Java
// A Java Program to implement Tarjan Offline LCA Algorithm
import java.util.Arrays;
class GFG
{
static final int V = 5;       // number of nodes in input tree
static final int WHITE = 1;      // COLOUR 'WHITE' is assigned value 1
static final int BLACK = 2;   // COLOUR 'BLACK' is assigned value 2
 
/* A binary tree node has data, pointer to left child
   and a pointer to right child */
static class Node
{
    int data;
    Node left, right;
};
 
/*
 subset[i].parent-.Holds the parent of node-'i'
 subset[i].rank-.Holds the rank of node-'i'
 subset[i].ancestor-.Holds the LCA queries answers
 subset[i].child-.Holds one of the child of node-'i'
                     if present, else -'0'
 subset[i].sibling-.Holds the right-sibling of node-'i'
                     if present, else -'0'
 subset[i].color-.Holds the colour of node-'i'
*/
static class subset
{
    int parent;
    int rank;
    int ancestor;
    int child;
    int sibling;
    int color;
};
 
// Structure to represent a query
// A query consists of (L,R) and we will process the
// queries offline a/c to Tarjan's oflline LCA algorithm
static class Query
{
    int L, R;
    Query(int L, int R)
    {
        this.L = L;
        this.R = R;
    }
};
 
/* Helper function that allocates a new node with the
   given data and null left and right pointers. */
static Node newNode(int data)
{
    Node node = new Node();
    node.data = data;
    node.left = node.right = null;
    return(node);
}
 
// A utility function to make set
static void makeSet(subset subsets[], int i)
{
    if (i < 1 || i > V)
        return;
    subsets[i].color = WHITE;
    subsets[i].parent = i;
    subsets[i].rank = 0;
    return;
}
 
// A utility function to find set of an element i
// (uses path compression technique)
static int findSet(subset subsets[], int i)
{
    // find root and make root as parent of i (path compression)
    if (subsets[i].parent != i)
        subsets[i].parent = findSet (subsets, subsets[i].parent);
 
    return subsets[i].parent;
}
 
// A function that does union of two sets of x and y
// (uses union by rank)
static void unionSet(subset subsets[], int x, int y)
{
    int xroot = findSet (subsets, x);
    int yroot = findSet (subsets, y);
 
    // Attach smaller rank tree under root of high rank tree
    // (Union by Rank)
    if (subsets[xroot].rank < subsets[yroot].rank)
        subsets[xroot].parent = yroot;
    else if (subsets[xroot].rank > subsets[yroot].rank)
        subsets[yroot].parent = xroot;
 
    // If ranks are same, then make one as root and increment
    // its rank by one
    else
    {
        subsets[yroot].parent = xroot;
        (subsets[xroot].rank)++;
    }
}
 
// The main function that prints LCAs. u is root's data.
// m is size of q[]
static void lcaWalk(int u, Query q[], int m,
             subset subsets[])
{
   
    // Make Sets
    makeSet(subsets, u);
 
    // Initially, each node's ancestor is the node
    // itself.
    subsets[findSet(subsets, u)].ancestor = u;
    int child = subsets[u].child;
 
    // This while loop doesn't run for more than 2 times
    // as there can be at max. two children of a node
    while (child != 0)
    {
        lcaWalk(child, q, m, subsets);
        unionSet (subsets, u, child);
        subsets[findSet(subsets, u)].ancestor = u;
        child = subsets[child].sibling;
    }
 
    subsets[u].color = BLACK;
    for (int i = 0; i < m; i++)
    {
        if (q[i].L == u)
        {
            if (subsets[q[i].R].color == BLACK)
            {
                System.out.printf("LCA(%d %d)->%d\n",
                  q[i].L,
                  q[i].R,
                  subsets[findSet(subsets,q[i].R)].ancestor);
            }
        }
        else if (q[i].R == u)
        {
            if (subsets[q[i].L].color == BLACK)
            {
                System.out.printf("LCA(%d %d)->%d\n",
                  q[i].L,
                  q[i].R,
                  subsets[findSet(subsets,q[i].L)].ancestor);
            }
        }
    }
    return;
}
 
// This is basically an inorder traversal and
// we preprocess the arrays. child[]
// and sibling[] in "subset" with
// the tree structure using this function.
static void preprocess(Node  node, subset subsets[])
{
    if (node == null)
        return;
 
    // Recur on left child
    preprocess(node.left, subsets);
 
    if (node.left != null && node.right != null)
    {
       
        /* Note that the below two lines can also be this-
        subsets[node.data].child = node.right.data;
        subsets[node.right.data].sibling =
                                         node.left.data;
 
        This is because if both left and right children of
        node-'i' are present then we can store any of them
        in subsets[i].child and correspondingly its sibling*/
        subsets[node.data].child = node.left.data;
        subsets[node.left.data].sibling =
            node.right.data;
 
    }
    else if ((node.left != null && node.right == null)
             || (node.left == null && node.right != null))
    {
        if(node.left != null && node.right == null)
            subsets[node.data].child = node.left.data;
        else
            subsets[node.data].child = node.right.data;
    }
 
    // Recur on right child
    preprocess (node.right, subsets);
}
 
// A function to initialise prior to pre-processing and
// LCA walk
static void initialise(subset subsets[])
{
   
    // We colour all nodes WHITE before LCA Walk.
    for (int i = 1; i < subsets.length; i++)
    {
        subsets[i] = new subset();
        subsets[i].color = WHITE;
    }
    return;
}
 
// Prints LCAs for given queries q[0..m-1] in a tree
// with given root
static void printLCAs(Node root, Query q[], int m)
{
   
    // Allocate memory for V subsets and nodes
    subset  []subsets = new subset[V + 1];
 
    // Creates subsets and colors them WHITE
    initialise(subsets);
 
    // Preprocess the tree
    preprocess(root, subsets);
 
    // Perform a tree walk to process the LCA queries
    // offline
    lcaWalk(root.data , q, m, subsets);
}
 
// Driver code
public static void main(String[] args)
{
    /*
     We cona binary tree :-
            1
          /  \
         2    3
       /  \
      4    5           */
 
    Node root = newNode(1);
    root.left = newNode(2);
    root.right = newNode(3);
    root.left.left = newNode(4);
    root.left.right = newNode(5);
 
    // LCA Queries to answer
    Query q[] =  new Query[3];
    q[0] = new Query(5, 4);
    q[1] = new Query(1, 3);
    q[2] = new Query(2, 3);
    int m = q.length;
    printLCAs(root, q, m);
}
}
 
// This code is contributed by gauravrajput1


Python3
# A Python3 program to implement Tarjan
# Offline LCA Algorithm
 
# Number of nodes in input tree
V = 5
 
# COLOUR 'WHITE' is assigned value 1
WHITE = 1
 
# COLOUR 'BLACK' is assigned value 2
BLACK = 2 
 
# A binary tree node has data, pointer
# to left child and a pointer to right child
class Node:
     
    def __init__(self):
 
        self.data = 0
        self.left = None
        self.right = None
 
'''
 subset[i].parent-.Holds the parent of node-'i'
 subset[i].rank-.Holds the rank of node-'i'
 subset[i].ancestor-.Holds the LCA queries answers
 subset[i].child-.Holds one of the child of node-'i'
                     if present, else -'0'
 subset[i].sibling-.Holds the right-sibling of node-'i'
                     if present, else -'0'
 subset[i].color-.Holds the colour of node-'i'
'''
class subset:
     
    def __init__(self):
 
        self.parent = 0
        self.rank = 0
        self.ancestor = 0
        self.child = 0
        self.sibling = 0
        self.color = 0
 
# Structure to represent a query
# A query consists of (L,R) and we
# will process the queries offline
# a/c to Tarjan's oflline LCA algorithm
class Query:
     
    def __init__(self, L, R):
 
        self.L = L
        self.R = R
 
# Helper function that allocates a new node
# with the given data and None left and
# right pointers.
def newNode(data):
 
    node = Node()
    node.data = data
    node.left = node.right = None
    return (node)
 
# A utility function to make set
def makeSet(subsets, i):
 
    if (i < 1 or i > V):
        return
 
    subsets[i].color = WHITE
    subsets[i].parent = i
    subsets[i].rank = 0
 
    return
 
# A utility function to find set of an element i
# (uses path compression technique)
def findSet(subsets, i):
 
    # Find root and make root as parent
    # of i (path compression)
    if (subsets[i].parent != i):
        subsets[i].parent = findSet(subsets,
                                    subsets[i].parent)
 
    return subsets[i].parent
 
# A function that does union of two sets
# of x and y (uses union by rank)
def unionSet(subsets, x, y):
 
    xroot = findSet(subsets, x)
    yroot = findSet(subsets, y)
 
    # Attach smaller rank tree under root of
    # high rank tree (Union by Rank)
    if (subsets[xroot].rank < subsets[yroot].rank):
        subsets[xroot].parent = yroot
    elif (subsets[xroot].rank > subsets[yroot].rank):
        subsets[yroot].parent = xroot
 
    # If ranks are same, then make one as root
    # and increment its rank by one
    else:
        subsets[yroot].parent = xroot
        (subsets[xroot].rank) += 1
 
# The main function that prints LCAs. u is
# root's data. m is size of q[]
def lcaWalk(u, q, m, subsets):
 
    # Make Sets
    makeSet(subsets, u)
 
    # Initially, each node's ancestor is the node
    # itself.
    subsets[findSet(subsets, u)].ancestor = u
 
    child = subsets[u].child
 
    # This while loop doesn't run for more than 2 times
    # as there can be at max. two children of a node
    while (child != 0):
        lcaWalk(child, q, m, subsets)
        unionSet(subsets, u, child)
        subsets[findSet(subsets, u)].ancestor = u
        child = subsets[child].sibling
 
    subsets[u].color = BLACK
 
    for i in range(m):
        if (q[i].L == u):
            if (subsets[q[i].R].color == BLACK):
 
                print("LCA(%d %d) -> %d" % (q[i].L, q[i].R,
                   subsets[findSet(subsets, q[i].R)].ancestor))
 
        elif (q[i].R == u):
            if (subsets[q[i].L].color == BLACK):
 
                print("LCA(%d %d) -> %d" % (q[i].L, q[i].R,
                   subsets[findSet(subsets, q[i].L)].ancestor))
 
    return
 
# This is basically an inorder traversal and
# we preprocess the arrays. child[]
# and sibling[] in "struct subset" with
# the tree structure using this function.
def preprocess(node, subsets):
 
    if (node == None):
        return
 
    # Recur on left child
    preprocess(node.left, subsets)
 
    if (node.left != None and node.right != None):
         
        ''' Note that the below two lines can also be this-
        subsets[node.data].child = node.right.data;
        subsets[node.right.data].sibling =
                                         node.left.data;
 
        This is because if both left and right children of
        node-'i' are present then we can store any of them
        in subsets[i].child and correspondingly its sibling'''
        subsets[node.data].child = node.left.data
        subsets[node.left.data].sibling = node.right.data
 
    elif ((node.left != None and node.right == None)
          or (node.left == None and node.right != None)):
 
        if (node.left != None and node.right == None):
            subsets[node.data].child = node.left.data
        else:
            subsets[node.data].child = node.right.data
 
    # Recur on right child
    preprocess(node.right, subsets)
 
# A function to initialise prior to pre-processing and
# LCA walk
def initialise(subsets):
 
    # Initialising the structure with 0's
    # memset(subsets, 0, (V+1) * sizeof(struct subset));
 
    # We colour all nodes WHITE before LCA Walk.
    for i in range(1, V + 1):
        subsets[i].color = WHITE
 
    return
 
# Prints LCAs for given queries q[0..m-1] in a tree
# with given root
def printLCAs(root, q, m):
 
    # Allocate memory for V subsets and nodes
    subsets = [subset() for _ in range(V + 1)]
 
    # Creates subsets and colors them WHITE
    initialise(subsets)
 
    # Preprocess the tree
    preprocess(root, subsets)
 
    # Perform a tree walk to process the LCA queries
    # offline
    lcaWalk(root.data, q, m, subsets)
 
# Driver code
if __name__ == "__main__":
     
    '''
     We construct a binary tree :-
            1
          /  \
         2    3
       /  \
      4    5           '''
 
    root = newNode(1)
    root.left = newNode(2)
    root.right = newNode(3)
    root.left.left = newNode(4)
    root.left.right = newNode(5)
 
    # LCA Queries to answer
    q = [Query(5, 4), Query(1, 3), Query(2, 3)]
    m = len(q)
 
    printLCAs(root, q, m)
 
# This code is contributed by sanjeev2552


C#
// A C# Program to implement Tarjan Offline LCA Algorithm
using System;
 
public class GFG
{
  static readonly int V = 5;       // number of nodes in input tree
  static readonly int WHITE = 1;      // COLOUR 'WHITE' is assigned value 1
  static readonly int BLACK = 2;   // COLOUR 'BLACK' is assigned value 2
 
  /* A binary tree node has data, pointer to left child
   and a pointer to right child */
  public
 
    class Node
    {
      public
 
        int data;
      public
 
        Node left, right;
    };
 
  /*
 subset[i].parent-.Holds the parent of node-'i'
 subset[i].rank-.Holds the rank of node-'i'
 subset[i].ancestor-.Holds the LCA queries answers
 subset[i].child-.Holds one of the child of node-'i'
                     if present, else -'0'
 subset[i].sibling-.Holds the right-sibling of node-'i'
                     if present, else -'0'
 subset[i].color-.Holds the colour of node-'i'
*/
  public
    class subset
    {
      public
        int parent;
      public
        int rank;
      public
        int ancestor;
      public
        int child;
      public
        int sibling;
      public
        int color;
    };
 
  // Structure to represent a query
  // A query consists of (L,R) and we will process the
  // queries offline a/c to Tarjan's oflline LCA algorithm
  public
    class Query
    {
      public
        int L, R;
      public
        Query(int L, int R)
      {
        this.L = L;
        this.R = R;
      }
    };
 
  /* Helper function that allocates a new node with the
   given data and null left and right pointers. */
  static Node newNode(int data)
  {
    Node node = new Node();
    node.data = data;
    node.left = node.right = null;
    return(node);
  }
 
  // A utility function to make set
  static void makeSet(subset []subsets, int i)
  {
    if (i < 1 || i > V)
      return;
    subsets[i].color = WHITE;
    subsets[i].parent = i;
    subsets[i].rank = 0;
    return;
  }
 
  // A utility function to find set of an element i
  // (uses path compression technique)
  static int findSet(subset []subsets, int i)
  {
 
    // find root and make root as parent of i (path compression)
    if (subsets[i].parent != i)
      subsets[i].parent = findSet (subsets, subsets[i].parent);
    return subsets[i].parent;
  }
 
  // A function that does union of two sets of x and y
  // (uses union by rank)
  static void unionSet(subset []subsets, int x, int y)
  {
    int xroot = findSet (subsets, x);
    int yroot = findSet (subsets, y);
 
    // Attach smaller rank tree under root of high rank tree
    // (Union by Rank)
    if (subsets[xroot].rank < subsets[yroot].rank)
      subsets[xroot].parent = yroot;
    else if (subsets[xroot].rank > subsets[yroot].rank)
      subsets[yroot].parent = xroot;
 
    // If ranks are same, then make one as root and increment
    // its rank by one
    else
    {
      subsets[yroot].parent = xroot;
      (subsets[xroot].rank)++;
    }
  }
 
  // The main function that prints LCAs. u is root's data.
  // m is size of q[]
  static void lcaWalk(int u, Query []q, int m,
                      subset []subsets)
  {
 
    // Make Sets
    makeSet(subsets, u);
 
    // Initially, each node's ancestor is the node
    // itself.
    subsets[findSet(subsets, u)].ancestor = u;
    int child = subsets[u].child;
 
    // This while loop doesn't run for more than 2 times
    // as there can be at max. two children of a node
    while (child != 0)
    {
      lcaWalk(child, q, m, subsets);
      unionSet (subsets, u, child);
      subsets[findSet(subsets, u)].ancestor = u;
      child = subsets[child].sibling;
    }
    subsets[u].color = BLACK;
    for (int i = 0; i < m; i++)
    {
      if (q[i].L == u)
      {
        if (subsets[q[i].R].color == BLACK)
        {
          Console.WriteLine("LCA(" + q[i].L + " " + q[i].R+") -> " +               
                            subsets[findSet(subsets, q[i].R)].ancestor);
        }
      }
      else if (q[i].R == u)
      {
        if (subsets[q[i].L].color == BLACK)
        {
          Console.WriteLine("LCA(" + q[i].L + " " + q[i].R + ") -> " +               
                            subsets[findSet(subsets, q[i].L)].ancestor);
        }
      }
    }
    return;
  }
 
  // This is basically an inorder traversal and
  // we preprocess the arrays. child[]
  // and sibling[] in "subset" with
  // the tree structure using this function.
  static void preprocess(Node  node, subset []subsets)
  {
    if (node == null)
      return;
 
    // Recur on left child
    preprocess(node.left, subsets);
 
    if (node.left != null && node.right != null)
    {
 
      /* Note that the below two lines can also be this-
        subsets[node.data].child = node.right.data;
        subsets[node.right.data].sibling =
                                         node.left.data;
 
        This is because if both left and right children of
        node-'i' are present then we can store any of them
        in subsets[i].child and correspondingly its sibling*/
      subsets[node.data].child = node.left.data;
      subsets[node.left.data].sibling =
        node.right.data;
 
    }
    else if ((node.left != null && node.right == null)
             || (node.left == null && node.right != null))
    {
      if(node.left != null && node.right == null)
        subsets[node.data].child = node.left.data;
      else
        subsets[node.data].child = node.right.data;
    }
 
    // Recur on right child
    preprocess (node.right, subsets);
  }
 
  // A function to initialise prior to pre-processing and
  // LCA walk
  static void initialise(subset []subsets)
  {
 
    // We colour all nodes WHITE before LCA Walk.
    for (int i = 1; i < subsets.Length; i++)
    {
      subsets[i] = new subset();
      subsets[i].color = WHITE;
    }
    return;
  }
 
  // Prints LCAs for given queries q[0..m-1] in a tree
  // with given root
  static void printLCAs(Node root, Query []q, int m)
  {
 
    // Allocate memory for V subsets and nodes
    subset  []subsets = new subset[V + 1];
 
    // Creates subsets and colors them WHITE
    initialise(subsets);
 
    // Preprocess the tree
    preprocess(root, subsets);
 
    // Perform a tree walk to process the LCA queries
    // offline
    lcaWalk(root.data, q, m, subsets);
  }
 
  // Driver code
  public static void Main(String[] args)
  {
    /*
     We cona binary tree :-
            1
          /  \
         2    3
       /  \
      4    5           */
 
    Node root = newNode(1);
    root.left = newNode(2);
    root.right = newNode(3);
    root.left.left = newNode(4);
    root.left.right = newNode(5);
 
    // LCA Queries to answer
    Query []q =  new Query[3];
    q[0] = new Query(5, 4);
    q[1] = new Query(1, 3);
    q[2] = new Query(2, 3);
    int m = q.Length;
    printLCAs(root, q, m);
  }
}
 
// This code is contributed by Rajput-Ji


输出 :

LCA(5 4) -> 2
LCA(2 3) -> 1
LCA(1 3) -> 1

时间复杂度:超线性,即几乎不比线性慢。 O(N + Q)时间,其中O(N)时间用于预处理,几乎O(1)时间用于回答查询。

辅助空间:我们使用了许多数组,如parent [],rank [],ancestor [],它们在不相交集合并运算中使用,每个数组的大小等于节点数。我们还使用arrays-child [],sibling [],color [],它们在此离线算法中很有用。因此,我们使用O(N)。
为方便起见,所有这些数组都放在一个结构-结构子集中来保存这些数组。