Prerequisite : Segment Tree
Persistency in Data Structure
段树本身就是一个很好的数据结构,在许多情况下都可以发挥作用。在这篇文章中,我们将介绍这种数据结构中的持久性的概念。坚持不懈,只是意味着保留更改。但是显然,保留更改会导致额外的内存消耗,从而影响时间复杂度。
我们的目标是在段树中应用持久性,并确保每次更改所花费的时间和空间都不会超过O(log n) 。
让我们从版本的角度来考虑,即,对于细分树中的每个更改,我们都会为其创建一个新版本。
我们将认为我们的初始版本为Version-0。现在,当我们在段树中进行任何更新时,我们将为其创建一个新版本,并以类似的方式跟踪所有版本的记录。
但是为每个版本创建整个树将占用O(n log n)的额外空间和O(n log n)的时间。因此,对于大量版本而言,这种想法会耗尽时间和内存。
让我们利用以下事实:对于段树中的每个新更新(为简单起见,使用点更新),都会修改at log logn节点。因此,我们的新版本将仅包含这些日志n个新节点,其余节点将与以前的版本相同。因此,很明显,对于每个新版本,我们只需要创建这些日志n个新节点,而其余节点可以与先前版本共享。
考虑下图以获得更好的可视化效果(单击图像以获得更好的视图):
考虑带有绿色节点的段树。让我们将此段树称为version-0 。每个节点的左子节点均以红色实线连接,而每个节点的右子节点均以紫色实线连接。显然,此段树由15个节点组成。
现在考虑我们需要在版本0的叶节点13中进行更改。
因此,受影响的节点将是–节点13,节点6,节点3,节点1 。
因此,对于新版本(Version-1),我们只需要创建这4个新节点。
现在,让我们为该段树中的更改构建版本1。我们需要一个新的节点1,因为它会受到节点13中所做的更改的影响。因此,我们将首先创建一个新的节点1′ (黄色)。版本0中节点1’的左子节点与节点1的左子节点相同。因此,我们将节点1’的左子节点与版本0的节点2(图中的红色虚线)相连。现在让我们检查版本1中节点1’的正确子代。由于受影响,我们需要创建一个新节点。因此,我们创建了一个称为节点3’的新节点,并使其成为节点1’的正确子节点(纯紫色边缘连接)。
现在以类似的方式检查节点3′ 。左子节点受到影响,因此我们创建一个名为节点6′的新节点,并将其与节点3’的红色实心边连接起来,其中节点3’的右子节点与版本3中的节点3的右子节点相同- 0。因此,我们将版本0中的节点3的右子作为版本1中的节点3’的右子(请参见紫色短划线)。
对节点6’执行相同的过程,我们看到节点6’的左子节点在版本0(红色虚线连接)中将是节点6的左子节点,而右子节点是新创建的称为节点13’的节点(纯紫色)虚线)。
每个黄色节点是一个新创建的节点,虚线边缘是段树的不同版本之间的相互连接。
现在,出现了一个问题:如何跟踪所有版本?
–我们只需要跟踪所有版本的第一个根节点,这将有助于跟踪不同版本中的所有新创建的节点。为此,我们可以维护一个指向所有版本的段树的第一个节点的指针数组。
让我们考虑一个非常基本的问题,看看如何在段树中实现持久性
Problem : Given an array A[] and different point update operations.Considering
each point operation to create a new version of the array. We need to answer
the queries of type
Q v l r : output the sum of elements in range l to r just after the v-th update.
我们将创建细分树的所有版本并跟踪其根节点,然后对于每个范围和查询,我们将在查询函数传递所需版本的根节点并输出所需的总和。
下面是上述问题的实现:
C++
// C++ program to implement persistent segment
// tree.
#include "bits/stdc++.h"
using namespace std;
#define MAXN 100
/* data type for individual
* node in the segment tree */
struct node
{
// stores sum of the elements in node
int val;
// pointer to left and right children
node* left, *right;
// required constructors........
node() {}
node(node* l, node* r, int v)
{
left = l;
right = r;
val = v;
}
};
// input array
int arr[MAXN];
// root pointers for all versions
node* version[MAXN];
// Constructs Version-0
// Time Complexity : O(nlogn)
void build(node* n,int low,int high)
{
if (low==high)
{
n->val = arr[low];
return;
}
int mid = (low+high) / 2;
n->left = new node(NULL, NULL, 0);
n->right = new node(NULL, NULL, 0);
build(n->left, low, mid);
build(n->right, mid+1, high);
n->val = n->left->val + n->right->val;
}
/**
* Upgrades to new Version
* @param prev : points to node of previous version
* @param cur : points to node of current version
* Time Complexity : O(logn)
* Space Complexity : O(logn) */
void upgrade(node* prev, node* cur, int low, int high,
int idx, int value)
{
if (idx > high or idx < low or low > high)
return;
if (low == high)
{
// modification in new version
cur->val = value;
return;
}
int mid = (low+high) / 2;
if (idx <= mid)
{
// link to right child of previous version
cur->right = prev->right;
// create new node in current version
cur->left = new node(NULL, NULL, 0);
upgrade(prev->left,cur->left, low, mid, idx, value);
}
else
{
// link to left child of previous version
cur->left = prev->left;
// create new node for current version
cur->right = new node(NULL, NULL, 0);
upgrade(prev->right, cur->right, mid+1, high, idx, value);
}
// calculating data for current version
// by combining previous version and current
// modification
cur->val = cur->left->val + cur->right->val;
}
int query(node* n, int low, int high, int l, int r)
{
if (l > high or r < low or low > high)
return 0;
if (l <= low and high <= r)
return n->val;
int mid = (low+high) / 2;
int p1 = query(n->left,low,mid,l,r);
int p2 = query(n->right,mid+1,high,l,r);
return p1+p2;
}
int main(int argc, char const *argv[])
{
int A[] = {1,2,3,4,5};
int n = sizeof(A)/sizeof(int);
for (int i=0; i
Java
// Java program to implement persistent
// segment tree.
class GFG{
// Declaring maximum number
static Integer MAXN = 100;
// Making Node for tree
static class node
{
// Stores sum of the elements in node
int val;
// Reference to left and right children
node left, right;
// Required constructors..
node() {}
// Node constructor for l,r,v
node(node l, node r, int v)
{
left = l;
right = r;
val = v;
}
}
// Input array
static int[] arr = new int[MAXN];
// Root pointers for all versions
static node version[] = new node[MAXN];
// Constructs Version-0
// Time Complexity : O(nlogn)
static void build(node n, int low, int high)
{
if (low == high)
{
n.val = arr[low];
return;
}
int mid = (low + high) / 2;
n.left = new node(null, null, 0);
n.right = new node(null, null, 0);
build(n.left, low, mid);
build(n.right, mid + 1, high);
n.val = n.left.val + n.right.val;
}
/* Upgrades to new Version
* @param prev : points to node of previous version
* @param cur : points to node of current version
* Time Complexity : O(logn)
* Space Complexity : O(logn) */
static void upgrade(node prev, node cur, int low,
int high, int idx, int value)
{
if (idx > high || idx < low || low > high)
return;
if (low == high)
{
// Modification in new version
cur.val = value;
return;
}
int mid = (low + high) / 2;
if (idx <= mid)
{
// Link to right child of previous version
cur.right = prev.right;
// Create new node in current version
cur.left = new node(null, null, 0);
upgrade(prev.left, cur.left, low,
mid, idx, value);
}
else
{
// Link to left child of previous version
cur.left = prev.left;
// Create new node for current version
cur.right = new node(null, null, 0);
upgrade(prev.right, cur.right, mid + 1,
high, idx, value);
}
// Calculating data for current version
// by combining previous version and current
// modification
cur.val = cur.left.val + cur.right.val;
}
static int query(node n, int low, int high,
int l, int r)
{
if (l > high || r < low || low > high)
return 0;
if (l <= low && high <= r)
return n.val;
int mid = (low + high) / 2;
int p1 = query(n.left, low, mid, l, r);
int p2 = query(n.right, mid + 1, high, l, r);
return p1 + p2;
}
// Driver code
public static void main(String[] args)
{
int A[] = { 1, 2, 3, 4, 5 };
int n = A.length;
for(int i = 0; i < n; i++)
arr[i] = A[i];
// Creating Version-0
node root = new node(null, null, 0);
build(root, 0, n - 1);
// Storing root node for version-0
version[0] = root;
// Upgrading to version-1
version[1] = new node(null, null, 0);
upgrade(version[0], version[1], 0, n - 1, 4, 1);
// Upgrading to version-2
version[2] = new node(null, null, 0);
upgrade(version[1], version[2], 0, n - 1, 2, 10);
// For print
System.out.print("In version 1 , query(0,4) : ");
System.out.print(query(version[1], 0, n - 1, 0, 4));
System.out.print("\nIn version 2 , query(3,4) : ");
System.out.print(query(version[2], 0, n - 1, 3, 4));
System.out.print("\nIn version 0 , query(0,3) : ");
System.out.print(query(version[0], 0, n - 1, 0, 3));
}
}
// This code is contributed by mark_85
C#
// C# program to implement persistent
// segment tree.
using System;
class node
{
// Stores sum of the elements in node
public int val;
// Reference to left and right children
public node left, right;
// Required constructors..
public node()
{}
// Node constructor for l,r,v
public node(node l, node r, int v)
{
left = l;
right = r;
val = v;
}
}
class GFG{
// Declaring maximum number
static int MAXN = 100;
// Making Node for tree
// Input array
static int[] arr = new int[MAXN];
// Root pointers for all versions
static node[] version = new node[MAXN];
// Constructs Version-0
// Time Complexity : O(nlogn)
static void build(node n, int low, int high)
{
if (low == high)
{
n.val = arr[low];
return;
}
int mid = (low + high) / 2;
n.left = new node(null, null, 0);
n.right = new node(null, null, 0);
build(n.left, low, mid);
build(n.right, mid + 1, high);
n.val = n.left.val + n.right.val;
}
/* Upgrades to new Version
* @param prev : points to node of previous version
* @param cur : points to node of current version
* Time Complexity : O(logn)
* Space Complexity : O(logn) */
static void upgrade(node prev, node cur, int low,
int high, int idx, int value)
{
if (idx > high || idx < low || low > high)
return;
if (low == high)
{
// Modification in new version
cur.val = value;
return;
}
int mid = (low + high) / 2;
if (idx <= mid)
{
// Link to right child of previous version
cur.right = prev.right;
// Create new node in current version
cur.left = new node(null, null, 0);
upgrade(prev.left, cur.left, low,
mid, idx, value);
}
else
{
// Link to left child of previous version
cur.left = prev.left;
// Create new node for current version
cur.right = new node(null, null, 0);
upgrade(prev.right, cur.right,
mid + 1, high, idx, value);
}
// Calculating data for current version
// by combining previous version and current
// modification
cur.val = cur.left.val + cur.right.val;
}
static int query(node n, int low, int high,
int l, int r)
{
if (l > high || r < low || low > high)
return 0;
if (l <= low && high <= r)
return n.val;
int mid = (low + high) / 2;
int p1 = query(n.left, low, mid, l, r);
int p2 = query(n.right, mid + 1, high, l, r);
return p1 + p2;
}
// Driver code
public static void Main(String[] args)
{
int[] A = { 1, 2, 3, 4, 5 };
int n = A.Length;
for(int i = 0; i < n; i++)
arr[i] = A[i];
// Creating Version-0
node root = new node(null, null, 0);
build(root, 0, n - 1);
// Storing root node for version-0
version[0] = root;
// Upgrading to version-1
version[1] = new node(null, null, 0);
upgrade(version[0], version[1], 0,
n - 1, 4, 1);
// Upgrading to version-2
version[2] = new node(null, null, 0);
upgrade(version[1], version[2], 0,
n - 1, 2, 10);
// For print
Console.Write("In version 1 , query(0,4) : ");
Console.Write(query(version[1], 0, n - 1, 0, 4));
Console.Write("\nIn version 2 , query(3,4) : ");
Console.Write(query(version[2], 0, n - 1, 3, 4));
Console.Write("\nIn version 0 , query(0,3) : ");
Console.Write(query(version[0], 0, n - 1, 0, 3));
}
}
// This code is contributed by sanjeev2552
输出:
In version 1 , query(0,4) : 11
In version 2 , query(3,4) : 5
In version 0 , query(0,3) : 10
注意:上述问题也可以通过以下方式解决:通过根据版本对查询进行脱机处理,并在相应的更新之后立即回答查询,来离线处理查询。
时间复杂度:时间复杂度将与段树中的查询和点更新操作相同,因为我们可以考虑在O(1)中完成额外的节点创建步骤。因此,新版本创建和范围和查询的每次查询的总体时间复杂度将为O(log n) 。