Tarjan的离线最小公祖先算法

先决条件: 生命周期评价基础 , 基于秩和路径压缩的不相交集并 我们得到一棵树(可以扩展为DAG),我们有许多形式为LCA(u,v)的查询,即查找节点“u”和“v”的LCA。 我们可以在中执行这些查询 O(N+QlogN)时间 使用RMQ ,其中O(N)时间用于预处理,O(logn)时间用于回答查询,其中 N=节点数和 Q=需要回答的问题数量。 我们能做得更好吗?我们能在线性(几乎)时间内完成吗?对 本文介绍了一种离线算法,该算法以大约10分钟的时间执行这些查询 O(N+Q)时间 尽管如此,这并不完全是线性的,因为时间复杂度分析中涉及到一个逆阿克曼函数。有关逆阿克曼函数的更多详细信息,请参阅 .作为总结,我们可以说,对于可以写入物理逆的任何输入大小值,逆阿克曼函数仍然小于4。因此,我们认为这几乎是线性的。 我们考虑输入树如下所示。我们将 预处理 树和填充两个数组- 孩子和兄弟姐妹 根据下面的解释-

null

tree1

让我们来处理这些查询- 生命周期评价(5,4),生命周期评价(1,3),生命周期评价(2,3) 现在,在预处理之后,我们执行 LCA步行 从树的根开始(这里是节点“1”)。但在LCA步行之前,我们用 白色 .在整个LCA过程中,我们使用了三个不相交的集合并集函数——makeSet()、findSet()、unionSet()。 这些函数使用按秩合并和路径压缩技术来提高运行时间。在生命周期评估过程中,我们的查询会得到处理和输出(以随机顺序)。在对整棵树进行LCA遍历后,所有节点都会着色 黑色 . Tarjan离线LCA算法步骤摘自CLRS,第21-3节,第584页,第2/3版。

tre22

注意:查询可能不会按原始顺序处理。我们可以很容易地修改流程,并根据输入顺序对其进行排序。 下面的图片清楚地描述了发生的所有步骤。红色箭头表示我们的旅行方向 递归函数LCA() .

tree3

tree4

tree5

tree6

从上面的图片中,我们可以清楚地看到,查询是按照以下顺序处理的: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 <bits/stdc++.h>
#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 offline 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" ,
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" ,
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 offline 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" ,
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" ,
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 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[] = 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 offline 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
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) + = 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))
else if (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
else if ((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 offline 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 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 = 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


Javascript

<script>
// A Javascript Program to implement Tarjan Offline LCA Algorithm
let V = 5; // number of nodes in input tree
let WHITE = 1; // COLOUR 'WHITE' is assigned value 1
let BLACK = 2; // COLOUR 'BLACK' is assigned value 2
/* A binary tree node has data, pointer to left child
and a pointer to right child */
class Node
{
constructor(data)
{
this .data = data;
this .left = this .right = null ;
}
}
/*
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
{
constructor()
{
this .parent = 0;
this .rank = 0;
this .ancestor = 0;
this .child = 0;
this .sibling = 0;
this .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 offline LCA algorithm
class Query
{
constructor(L, R)
{
this .L = L;
this .R = R;
}
}
/* Helper function that allocates a new node with the
given data and null left and right pointers. */
function newNode(data)
{
let node = new Node();
node.data = data;
node.left = node.right = null ;
return (node);
}
// A utility function to make set
function makeSet(subsets,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)
function 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)
function unionSet(subsets, x, y)
{
let xroot = findSet (subsets, x);
let 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[]
function 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;
let 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 (let i = 0; i < m; i++)
{
if (q[i].L == u)
{
if (subsets[q[i].R].color == BLACK)
{
document.write( "LCA(" + q[i].L+ " " +q[i].R+ ") -> " ,subsets[findSet(subsets,q[i].R)].ancestor+ "<br>" );
}
}
else if (q[i].R == u)
{
if (subsets[q[i].L].color == BLACK)
{
document.write( "LCA(" + q[i].L+ " " +q[i].R+ ") -> " ,subsets[findSet(subsets,q[i].L)].ancestor+ "<br>" );
}
}
}
return ;
}
// This is basically an inorder traversal and
// we preprocess the arrays. child[]
// and sibling[] in "subset" with
// the tree structure using this function.
function preprocess(node,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
function initialise(subsets)
{
// We colour all nodes WHITE before LCA Walk.
for (let 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
function printLCAs(root, q, m)
{
// Allocate memory for V subsets and nodes
let subsets = new Array(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
/*
We construct a binary tree :-
1
/
2    3
/
4    5           */
let 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
let q = new Array(3);
q[0] = new Query(5, 4);
q[1] = new Query(1, 3);
q[2] = new Query(2, 3);
let m = q.length;
printLCAs(root, q, m);
// This code is contributed by patel2127
</script>


输出:

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

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

辅助空间: 我们使用许多数组——父数组[]、秩数组[]、祖先数组[],这些数组用于不相交的集并运算,每个数组的大小等于节点数。我们还使用了数组-child[]、sibling[]、color[],它们在这个离线算法中很有用。因此,我们使用O(N)。 为了方便起见,所有这些数组都放在一个structure-struct子集中以保存这些数组。

工具书类 https://en.wikipedia.org/wiki/Tarjan%27s_off-行\最低\公共\祖先\算法 CLRS,第21-3节,第584页,第二版/第三版 http://wcipeg.com/wiki/Lowest_common_ancestor#Offline 本文由 拉希特·贝尔瓦里亚 .如果你喜欢GeekSforgek,并想贡献自己的力量,你也可以写一篇文章,然后将文章邮寄给评论-team@geeksforgeeks.org.看到你的文章出现在Geeksforgeks主页上,并帮助其他极客。 如果您发现任何不正确的地方,或者您想分享有关上述主题的更多信息,请写评论

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享