treap--'s famous random binary search tree, with excellent performance and simple implementation in the Oier widely circulated.
This blog introduces a non-rotating operation to maintain the treap, that is, no spin treap, also known as Fhq-treap.
Its ingenious is that only need to separate and merge the two basic operations, you can achieve any of the balance tree commonly used modification operations.
A feature that does not need to be rotated also makes it unnecessary to write code that takes into account multiple situations and the complexity of updating a father's son relationship, while reducing the complexity of the time.
In addition, it can be easily supported for persistence, which is really powerful.
Then systematically introduce the principle and implementation of non-rotating treap, and finally explain the application and examples.
What is Treap?
Treap is a binary search tree that satisfies the sequential traversal that is always ordered.
Each node of the treap, in addition to saving information, also holds a value \ (pri\).
This \ (pri\) stores the priority of this node.
What does it do with it?
In fact, treap, in addition to being a binary lookup tree, is a heap in another sense.
Child nodes of each node must satisfy the child node \ (pri\) smaller than the parent node \ (pri\). \ (pri\) The bigger the point the more upward.
What's the point? Why do you define this?
In fact, the \ (pri\) value is randomly assigned by the program, and the \ (pri\) value of each point is independent of the weight of the point and is random.
This randomization ensures that the depth of the treap is \ (O (log\;n) \), which is the power of randomization.
and can ensure that such achievements will not appear contradictions, now simulate the process of achievement:
First of all the nodes in accordance with the sequence traversal, and then find where \ (pri\) the largest, it as the root of the entire treap, the left side of the node to form the left subtree, the right side of the node to form the right sub-tree. Then the left and right sub-tree recursive processing.
This will eventually build a treap.
Second, the basic operation of Treap
Having finished the definition of treap, let's take a look at the two basic operations of TREAP:
Split and merge (merge).
Separation: Refers to a treap in accordance with the sequence of sequential traversal, divided into the left and right two halves, to meet the left and right two halves of the treap of all the values are unchanged.
Merge: Refers to merging two treap (typically from the original treapsplit), in the order in which they are traversed, and with the values of all nodes intact.
Split operation is relatively simple, first talk about how to achieve: Of course, before split to specify a value of K, indicating that split out of the treap in the middle sequence of the number of the first to split out of the treap.
From the root of this treap, see if its left subtree size is greater than or equal to K, if so, then the right subtree and root are in the second tree, continue to recursion to the left subtree.
If not, then the Zoki is in the first Treao, continue to recursion to the right subtree, and k to subtract the size of the left subtree plus one.
Code:
void Split (int rt,int k,int&rt1,int&rt2) {if (!rt) {rt1=rt2=0; return;} if (K<=siz[ls[rt]]) {Split (LS[RT],K,RT1,RT2); Ls[rt]=rt2;combine (RT); rt2=rt;} Else{split (RS[RT],K-SIZ[LS[RT]]-1,RT1,RT2); Rs[rt]=rt1;combine (RT); rt1=rt;}}
Where RT is the root, K is the size of the first subtrees tree, Rt1 and Rt2 used to return.
The Combine function is used to maintain the size of the node.
Next look at the merge operation:
There are two treap, suppose to put the second tree behind the first tree, then how to merge it?
Consider the \ (pri\) value of two root nodes, because the first one is in front of the second, so otherwise rt1 (the root of the first tree) in the left subtree of the rt2 (root of the second tree), otherwise rt2 in the right subtree of the RT1.
But because of the impact of \ (pri\), so can only Rt1 and Rt2 (pri\) The larger one as the root.
If Rt1 is the root, then there are Rt1 right subtrees and Rt2 merged as the right subtree of Rt1 now.
If Rt2 is the root, then there are Rt2 Zuozi and Rt1 merged as Rt2 's now left subtree.
Both cases are recursive into the subtree.
The code is as follows:
int Merge (int rt1,int rt2) {if (!rt1) return rt2;if (!RT2) return rt1;if (Pri[rt1]<pri[rt2]) {Rs[rt1]=merge (rs[rt1],rt2 ); combine (rt1); return rt1;} Else{ls[rt2]=merge (Rt1,ls[rt2]); combine (rt2); return rt2;}}
This is the basic operation of the two kinds of treap, which should be noticed when the tree is empty, and do special processing.
Third, the main operation
After reading these two operations, you will certainly ask: Is there a ghost to use?
Unable to insert Delete query the second ranking position of the balance tree, can only be on and off the balance tree, I do not!
However, all of the above operations can actually be achieved through these two basic operations:
The number of queries that are less than or equal to Val: The value of the root node and Val, if Val Small root node, recursively into the left subtree, otherwise recursive into the right sub-tree.
This query operation is named rank, code:
int rank (int rt,int v) {if (!RT) return 0;if (V<val[rt]) return Rank (LS[RT],V); else return Siz[ls[rt]]+rank (rs[rt],v) +1 ;}
Insert Val: First query Rank (val), then rank the whole treapsplit into two, and Val into a new node, merge into the inside can.
void Insert (int v) {val[++cnt]=v, Pri[cnt]=ran (), Siz[cnt]=1;int Rank=rank (root,v); int rt1,rt2; Split (ROOT,RANK,RT1,RT2); Root=merge (Merge (rt1,cnt), rt2);}
Delete val: First query Rank (val), then rank the whole treapsplit into three, delete the required points, and the final merge will be two remaining.
void Delete (int v) {int Rank=rank (root,v); int rt1,rt2,rt3,tmp; Split (ROOT,RANK,RT1,RT2); Split (RT1,RANK-1,RT3,TMP); Root=merge (RT3,RT2);//Memory Recycle?}
Query the K value: the entire treapsplit into three, output the required value, and finally merged together.
int Kth (int k) {int rt1,rt2,rt3,c; Split (ROOT,K,RT1,RT2); Split (RT1,K-1,RT3,C); Root=merge (Rt3,merge (C,RT2)); return val[c];}
Precursor: Kth (Rank (VAL-1)).
Successor: Kth (Rank (val) +1).
There are a lot of things to do, for everyone to fill up the brain.
Four, interval operation
The more important difference between the non-rotating treap and the rotary treap is that the non-rotating treap can easily support the operation of the interval.
How can I support it? You have seen, split operation is divided into a range ah! Separate a whole treap out of an interval, and modify it as you please. But remember to write a good interval pushdown like a line tree!
such as the interval reversal, is the left and right sub-tree exchange, and marking. Interval subtraction let alone.
Remember to add a pushdown! to the Split,merge and rank three functions inside
Five, practical application
There are many problems of the balance tree, can be solved with treap.
Recommended several questions:
Luogu P3369 Test your treap proficiency in normal operations.
Application of Luogu P3391 interval operation.
Luoge P3165 interval operation and other tricks.
"Algorithmic Learning" fhq-treap (non-rotating treap)