📜  持久段树|设置1(简介)

📅  最后修改于: 2021-04-17 11:35:54             🧑  作者: Mango

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个新节点,而其余节点可以与先前版本共享。

考虑下图以获得更好的可视化效果(单击图像以获得更好的视图):

持久segtree

考虑带有绿色节点的段树。让我们将此段树称为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)