Skip to content

Commit 484c803

Browse files
LeefrosttcNickolas
authored andcommitted
Add Treap article to Data structures section (cp-algorithms#89)
1 parent 5023769 commit 484c803

2 files changed

Lines changed: 263 additions & 0 deletions

File tree

src/data_structures/treap.md

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
<!--?title Treap -->
2+
#Treap (Cartesian tree)
3+
4+
Treap is a data structure which are combains binary tree and binary heap (that's why this structure called Treap => tree + heap).
5+
6+
In more strict way, Treap is a data structure that stores pair (X,Y) in a form of binary tree, so that it is a binary search tree by X and binary heap by Y.
7+
Assuming that all X and all Y are different, we can find that if a some tree’s element contains (X0, Y0), then all elements in the left subtree have X < X0 and all elements in right subtree have X > X0, and also, left and right subtree have Y < Y0.
8+
9+
Treaps have been proposed Siedel and Aragon in 1989.
10+
11+
##Advantages of such data organisation
12+
13+
In the application, which we are considering (we consider deramidy as a Treap) X values are the keys (also at same time they are values, stored in this data structure) and Y values are **priorities**. If there were no **priorities**, it would be a normal binary search tree by X and, a some set of X could correspond to a lot of trees, some of which are degenerate (eg, in the form of a chain), and, therefore, they are extremely slow (the main operations would be carried out of O (N)).
14+
15+
At the same time, **priorities** allow you to **uniquely** specify the tree that will be build (of course, it does not depend on the order of addition of components)(this is proved by the corresponding theorem). Now it is obvious that if you **choose a priorities by accident**, then, you will achieve the construction of degenerate trees in the average case, which will provide the asymptotic behaviour of O (log N) on average. Hence it is clear that the another name of this data structure - a **randomized binary search tree**.
16+
17+
##Operations
18+
So, Treap have next operations:
19+
20+
- **Insert(X,Y)**.
21+
Average asymptotic - O (log N).
22+
It performs the addition of a new element in the tree. The variant, described below, contains code, in which Y priority value is not transmitted function and chosen randomly (but bear in mind that it should not coincide with any other Y in the tree).
23+
- **Search (X)**.
24+
Average asymptotic - O (log N).
25+
Searches for an item with the specified key X. Implemented in exactly the same way as for an ordinary binary tree.
26+
- **Erase (X)**.
27+
Average asymptotic - O (log N).
28+
Searches for an item and remove it from the tree.
29+
- **Build (X1, ... Xn)**.
30+
Average asymptotic - O (N).
31+
Build a tree from a list of values. This operation can be implemented in linear time (assuming that the values of X1, ... Xn are sorted), but here, this implementation will not be discussed. We will used a simplified variant with serial calls of **Insert** operation.
32+
- **Union (T1, T2)**.
33+
Average asymptotic - O(M log (N/M)).
34+
It merges two trees, on the assumption that all the elements are different (however, this operation can be realized with the same asymptotic behavior, if the it is necessary to remove duplicate entries).
35+
- **Intersect (T1, T2)**.
36+
Average asymptotic - O (log N).
37+
Finds the intersection of two trees (ie, their common elements). Here the implementation of this operation will not be considered.
38+
39+
In addition, due to the fact that the Cartesian tree is a binary searching tree, some operation are also applicable to it such as finding the K-th largest element, and, on the contrary, the searching of item number.
40+
41+
##Description of implementations
42+
In terms of implementation, each element contains the X, Y and pointers to the left (L) and right (R) son.
43+
**Split** and **Merge** operations are required for implementation.
44+
45+
**Split(T,X)** - separates T tree on 2 parts - L and R trees (which are the returned values from operation). L contains all elements with key Xl < X, R contains all elements with key Xr >X. This operation is performed for the O (log N). The implementation is a quite simple - an obvious recursion.
46+
47+
**Merge(T1, T2)** - combines two subtrees T1 and T2, and returns the new tree. This operation is also performed for O (log N). It works under the assumption that the T1 and T2 have respective order (all X values in first tree **must be smaller** than X values in second tree). Thus, we need to combine these trees without reordering them by Y priorities. To do this, simply choose as the root a tree with the highest Y priority, and recursively calls itself from the other tree and the corresponding son of selected one.
48+
49+
Now, implementation of **Insert (X, Y)** is pretty obvious. First, we go down on the tree (as in a conventional binary tree search by X), but we should stop at the first element in which the priority value is less than the Y. We found a position where we will insert our element. Now, we call **Split (X)** on the found item (on the element along with all of its subtree). As the result of **Split** we will get L and R sons for new element which we are trying to add.
50+
51+
Also, implementation of **Erase (X)** is more understandable now. We go down on a tree (as in a normal binary search tree by X), seeking to delete the item during the way. When item found, we call a **Merge** operation on it sons - R and L trees, and after -paste result of Merge on a place of this item.
52+
53+
**Build** operation we will implement in a way with O(N log N) asymptotic by simply calling of **Insert** operation.
54+
55+
Finally, the **Union (T1, T2)** operation. Theoretically, its asymptotic behavior of O (M log (N / M)), but in practice it works very well, probably with a very small hidden constant. Suppose, without loss of generality, T1-> Y > T2-> Y, ie, T1 is the root of the root of the result. To get results, we need to combine trees T1-> L, T1-> R and T2 in 2 tree, so that they could be a sons of T1 tree. To do this, call the Split (T2, T1-> X), and we split T2 in the two part - L and R, which are then recursively combine sons of T1: Union (T1-> L, L) and Union (T1-> R, R), thus we will build the Left and Right subtrees of the result.
56+
57+
##Implementation
58+
59+
Let's make a realization, described above:
60+
```
61+
struct item {
62+
int key, prior;
63+
item * l, * r;
64+
item() { }
65+
item (int key, int prior) : key(key), prior(prior), l(NULL), r(NULL) { }
66+
};
67+
typedef item * pitem;
68+
69+
void split (pitem t, int key, pitem & l, pitem & r) {
70+
if (!t)
71+
l = r = NULL;
72+
else if (key < t->key)
73+
split (t->l, key, l, t->l), r = t;
74+
else
75+
split (t->r, key, t->r, r), l = t;
76+
}
77+
78+
void insert (pitem & t, pitem it) {
79+
if (!t)
80+
t = it;
81+
else if (it->prior > t->prior)
82+
split (t, it->key, it->l, it->r), t = it;
83+
else
84+
insert (it->key < t->key ? t->l : t->r, it);
85+
}
86+
87+
void merge (pitem & t, pitem l, pitem r) {
88+
if (!l || !r)
89+
t = l ? l : r;
90+
else if (l->prior > r->prior)
91+
merge (l->r, l->r, r), t = l;
92+
else
93+
merge (r->l, l, r->l), t = r;
94+
}
95+
96+
void erase (pitem & t, int key) {
97+
if (t->key == key)
98+
merge (t, t->l, t->r);
99+
else
100+
erase (key < t->key ? t->l : t->r, key);
101+
}
102+
103+
pitem unite (pitem l, pitem r) {
104+
if (!l || !r) return l ? l : r;
105+
if (l->prior < r->prior) swap (l, r);
106+
pitem lt, rt;
107+
split (r, l->key, lt, rt);
108+
l->l = unite (l->l, lt);
109+
l->r = unite (l->r, rt);
110+
return l;
111+
}
112+
```
113+
114+
##Supporting of the size of subtrees
115+
116+
To extend the functionality of the Cartesian tree, it is often necessary for each node to store the number of nodes in its subtree - field `int cnt` in the item structure. For example, K-th largest element of tree can easily be found for O (log N), or, conversely, for the same asymptotic behavior item number can be found in the sorted list (the implementation of these operations will not be differ from their realization for conventional binary search tree).
117+
118+
When a tree changes (add or remove items, etc.), _cnt_ should be also updated accordingly for vertices. For that purposes we should create two functions - `cnt ()` - it will return the current value of _cnt_, or 0 if the node does not exist, and `upd_cnt ()` - it will update the value _cnt_ for this vertex, with the condition - for her sons, L and R, the `cnt` has correctly updated. Then, of course, enough to add calls of `upd_cnt ()` to the end of each functions - insert, erase, split, merge - to keep the correct value of _cnt_.
119+
120+
```
121+
int cnt (pitem t) {
122+
return t ? t->cnt : 0;
123+
}
124+
125+
void upd_cnt (pitem t) {
126+
if (t)
127+
t->cnt = 1 + cnt(t->l) + cnt (t->r);
128+
}
129+
```
130+
131+
## Build a Treap for a O (N) in offline mode
132+
133+
TODO
134+
135+
## Implicit Treaps
136+
137+
Implicit Treap - is a simple modification of the conventional Cartesian tree, which, however, is a very powerful data structure. In fact, the implicit Treap can be perceived as an array, which can be implemented over the following procedures (all in O (log N) in the online mode):
138+
139+
- Inserting an element in the array in any position
140+
- Removal of any element
141+
- The amount of minimum / maximum on an arbitrary interval, etc.
142+
- Addition, painting on a segment
143+
- Coup (permutation of the elements in reverse order) in the interval
144+
145+
The key idea is that the key in this case will be **index** of the element inside array. But obviously, we will not store these _key_ values (otherwise, for example, when inserting an element operation would have had to change the key in O (N) vertices of the tree ).
146+
147+
Note that in this case the key to some vertices is actually the number of vertices less than it.
148+
Also note that the vertexes which are less than current can situated not only in left tree but also in the left subtrees in its ancestors. More precisely, the **implicit key** for some vertex t is the number of vertices cnt (T-> L) in the left subtree of this node plus a similar magnitude cnt (P-> L) +1 for each ancestor of the vertex P, provided that T is in the right subtree of P.
149+
150+
Now is more clearly how to quickly calculate the implicit key of current vertex. As in all operations, we arrive in any top when we goes down on the tree so we can just accumulate this amount and transfer it to functions. If we go to the left subtree - accumulated amount does not change, and if we go to the right - it increases in the cnt (T-> L) +1.
151+
152+
We give new implementations for **Split** and **Merge** operations:
153+
154+
```
155+
void merge (pitem & t, pitem l, pitem r) {
156+
if (!l || !r)
157+
t = l ? l : r;
158+
else if (l->prior > r->prior)
159+
merge (l->r, l->r, r), t = l;
160+
else
161+
merge (r->l, l, r->l), t = r;
162+
upd_cnt (t);
163+
}
164+
165+
void split (pitem t, pitem & l, pitem & r, int key, int add = 0) {
166+
if (!t)
167+
return void( l = r = 0 );
168+
int cur_key = add + cnt(t->l); //implicit key
169+
if (key <= cur_key)
170+
split (t->l, l, t->l, key, add), r = t;
171+
else
172+
split (t->r, t->r, r, key, add + 1 + cnt(t->l)), l = t;
173+
upd_cnt (t);
174+
}
175+
```
176+
177+
Now we turn to the implementation of various additional operations on the implicit Cartesian trees:
178+
179+
- **Insert element**.
180+
Suppose we need to insert an element at position _pos_. We divide Treap into two halves: a corresponding array [0..pos-1] and array [pos..sz]; it is enough for a split (T, T1, T2, pos). Then we can combine trees with a new vertex T1; it is enough for a merge (T1, T1, new_item) (it is easy to make sure that all of the preconditions are done). Finally, we combine the two trees T1 and T2 back into T - by a call of merge (T, T1, T2).
181+
- **Delete an item**.
182+
It is still easy: just find an item to delete, and then perform the merge to it sons, L and R, and put a result of merge operation in a place of T. In fact, there is no difference between the removal of the implicit Cartesian tree and removal of the conventional Cartesian tree.
183+
- **The amount / minimum**, etc. on the segment.
184+
Firstly, create a vertex for each additional item field F in the structure to store the value of the goal function for this peek’s subtree. This field is easy to maintain - same operation like during the work with _cnt_ (we need 2 functions for getting and settings this value and we should update depended function - see above).
185+
Secondly, we need to know how to respond to a request for an arbitrary interval [A; B].
186+
For this we need to get a part of tree which are corresponded to the segment [A;B]. It is easy to understand that this is enough for first split(T, T1, T2, A), and then split (T2, T2, T3, B-A+ 1). As a result, T2 will consist of all the elements in the interval [A; B], and only of them. Therefore, response to the request will be in the field F of T2 tree. After the response to the request, tree need to be restored by call of a merge (T, T1, T2) and merge (T, T, T3).
187+
- **Addition / painting on the segment**.
188+
Here we act as in the previous paragraph, but instead of the field F will store a field called _add_ that will contain accumulation value for painting. Before carrying out any operations necessary to set this value correctly - ie, accordingly change T->L->add and T-> R-> add, and clean up _add_ in parent node. In this way we will achieve that after any changes to the tree all information will not be lost.
189+
- **Flip on the segment**.
190+
This item is almost similar to the previous - just add the field ‘bool rev’, which will be setted to true, when you want to make a flip in the subtree of the current node. The initialize operation is just little bit complicated - we swapping a sons of this node and set this flag for them.
191+
192+
**Implementation**. Let us give an example for the full realization of the implicit Cartesian tree with the coup on the segment. Here, for each vertex we store field called _value_ - the actual value of the item standing in the array at the current position. We also give the implementation of the function ‘output ()’, which displays an array that corresponds to the current state of the implicit Cartesian tree.
193+
194+
```
195+
typedef struct item * pitem;
196+
struct item {
197+
int prior, value, cnt;
198+
bool rev;
199+
pitem l, r;
200+
};
201+
202+
int cnt (pitem it) {
203+
return it ? it->cnt : 0;
204+
}
205+
206+
void upd_cnt (pitem it) {
207+
if (it)
208+
it->cnt = cnt(it->l) + cnt(it->r) + 1;
209+
}
210+
211+
void push (pitem it) {
212+
if (it && it->rev) {
213+
it->rev = false;
214+
swap (it->l, it->r);
215+
if (it->l) it->l->rev ^= true;
216+
if (it->r) it->r->rev ^= true;
217+
}
218+
}
219+
220+
void merge (pitem & t, pitem l, pitem r) {
221+
push (l);
222+
push (r);
223+
if (!l || !r)
224+
t = l ? l : r;
225+
else if (l->prior > r->prior)
226+
merge (l->r, l->r, r), t = l;
227+
else
228+
merge (r->l, l, r->l), t = r;
229+
upd_cnt (t);
230+
}
231+
232+
void split (pitem t, pitem & l, pitem & r, int key, int add = 0) {
233+
if (!t)
234+
return void( l = r = 0 );
235+
push (t);
236+
int cur_key = add + cnt(t->l);
237+
if (key <= cur_key)
238+
split (t->l, l, t->l, key, add), r = t;
239+
else
240+
split (t->r, t->r, r, key, add + 1 + cnt(t->l)), l = t;
241+
upd_cnt (t);
242+
}
243+
244+
void reverse (pitem t, int l, int r) {
245+
pitem t1, t2, t3;
246+
split (t, t1, t2, l);
247+
split (t2, t2, t3, r-l+1);
248+
t2->rev ^= true;
249+
merge (t, t1, t2);
250+
merge (t, t, t3);
251+
}
252+
253+
void output (pitem t) {
254+
if (!t) return;
255+
push (t);
256+
output (t->l);
257+
printf ("%d ", t->value);
258+
output (t->r);
259+
}
260+
```
261+
262+

src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ especially popular in field of competitive programming.*
2424

2525
### Data Structure
2626
- [Fenwick Tree](./data_structures/fenwick.html)
27+
- [Treap](./data_structures/treap.html)
2728

2829
### String Processing
2930
- [String Hashing](./string/string-hashing.html)

0 commit comments

Comments
 (0)