![]() |
VOOZH | about |
Persistent data structures are a powerful tool in computer science, enabling us to maintain and access multiple versions of a data structure over time. One such structure is the Persistent Segment Tree. Segment trees are versatile data structures that allow efficient querying and updating of array intervals. By making a segment treepersistent, we enhance its capability to maintain historical versions, which is particularly useful in competitive programming and real-time applications where rollback and point-in-time queries are needed. This article explores the concept, implementation in Python.
Persistence in data structures refers to the ability to maintain access to previous versions of the data structure even after modifications. This can be achieved using techniques such as path copying, where only the parts of the structure that need to be changed are copied, thus saving space and time.
Each node in a Persistent Segment Tree contains:
Persistent Segment Trees allow you to perform updates and queries efficiently while preserving the history of changes. Here are the primary operations you can perform on a Persistent Segment Tree:
The build function constructs the segment tree from an array. This is similar to building a regular segment tree but sets up the foundation for persistence.
Algorithm:
left == right), create a leaf node with the value of that element.Implementation:
The update function creates a new version of the segment tree by copying only the necessary parts. It returns a new root node for the updated version, leaving the previous versions intact.
Algorithm:
left == right), create a new node with the updated value.Implementation:
The query function retrieves information from a segment of the tree. It can perform range queries on any version of the tree.
Algorithm:
Implementation:
Let's walk through a detailed example step-by-step to illustrate how the persistent segment tree works:
Initial Array
- arr = [1,2,3,4,5]
Building the Initial Segment Tree
- Build the leaf nodes:
- Leaf node for
arr[0]: value = 1- Leaf node for
arr[1]: value = 2- Leaf node for
arr[2]: value = 3- Leaf node for
arr[3]: value = 4- Leaf node for
arr[4]: value = 5- Build the internal nodes:
- Node for range [0, 1]: value = 1 + 2 = 3
- Node for range [2, 2]: value = 3
- Node for range [3, 4]: value = 4 + 5 = 9
- Node for range [0, 2]: value = 3 + 3 = 6
- Node for range [0, 4]: value = 6 + 9 = 15
Updating the Segment Tree
- Update
arr[2]from 3 to 10:
- Create new leaf node for
arr[2]: value = 10- Update node for range [2, 2]: new value = 10
- Update node for range [0, 2]: new value = 3 + 10 = 13
- Update node for range [0, 4]: new value = 13 + 9 = 22
Querying the Segment Tree
- Query range [1, 3] in the original tree:
- Node for range [1, 3] overlaps with range [0, 4], [0, 2], and [3, 4]
- Query range [1, 3] results in sum = 2 + 3 + 4 = 9
- Query range [1, 3] in the updated tree:
- Node for range [1, 3] overlaps with range [0, 4], [0, 2], and [3, 4]
- Query range [1, 3] results in sum = 2 + 10 + 4 = 16
This example demonstrates how the persistent segment tree maintains different versions efficiently and supports range queries on any version.
Below is the complete implementation of a Persistent Segment Tree in Python:
9 16
| Operation | Time Complexity | Space Complexity (per update) | Description |
|---|---|---|---|
| Build | O(n log n) | O(n log n) | Construct the initial segment tree from an array of size n |
| Update | O(log n) | O(log n) | Create a new version of the tree with an updated value at a specific index |
| Query | O(log n) | O(1) | Perform a range query on any version of the tree |