treap詳解
一、二叉排序樹
因為只要是來講treap的,所以關於二叉排序樹的知識就不再贅述。
如果還不知道二叉排序樹,可以先到別處學學再來看。
在二叉排序樹中,我們將比該節點小的值放在該節點的左邊,將比該節點大的值放在該節點的右邊。
可是很顯然,這樣的話操作的時間復雜度就和樹的深度有很大的關系。當樹的形態為一條鏈的時候,就無法滿足操作的復雜度為\(nlog(n)\)。
?
?
二、treap是什麽
treap = tree + heap
treap就是樹與堆的結合體。為了防止樹被單調的數據卡成一條鏈,可以給每個節點多加上一個隨機的優先值,讓樹同時也滿足堆的性質(一般為小根堆)。
由於優先值是隨機的,所以樹的深度平均為\(nlog(n)\)
?
?
三、treap實現
1、左旋(zip)與右旋(zap)
我們發現,在一個節點的優先值被隨機生成後,樹可能無法滿足我們所要求的堆的性質。
所以為了讓樹同時也具備這個性質,需要對樹進行一定的旋轉,使樹變成我們所希望的樣子。
?
左旋:將根節點的右兒子作為根,根節點作為右兒子的左兒子。
右旋:將根節點的左兒子作為根,根節點作為左兒子的右兒子。
由圖中可知左旋與右旋的操作方法。
void zig(int &k) { //左旋 int v = rson[k]; rson[k] = lson[v]; lson[v] = k; size[v] = size[k]; up(k); k = v; } void zag(int &k) { //右旋 int v = lson[k]; lson[k] = rson[v]; rson[v] = k; size[v] = size[k]; up(k); k = v; }
?
?
2、插入操作
我們考慮在樹上插入一個節點。
先像二叉排序樹一樣找到應當插入節點的位置,插入該節點,並初始化該節點的值。
由於樹要滿足小根堆的性質,所以如果插入的節點的優先級小於它的根節點,就通過左旋/右旋進行調整。(不理解的可以看看上面那張圖)
void insert(int &k, int x) { //插入節點 if (k == 0) { //如果節點數為0,直接插入 k = ++ cnt; size[k] = c[k] = 1; a[k] = x; pri[k] = rand(); return; } size[k] ++; if (a[k] == x) c[k] ++; else if (x > a[k]) { insert(rson[k], x); if (pri[rson[k]] < pri[k]) zig(k); } else { insert(lson[k], x); if (pri[lson[k]] < pri[k]) zag(k); } }
?
?
3、刪除操作
考慮在樹上刪除一個節點。
首先先在樹上找到該點。
對於這個點,有兩種情況:要麽它有一個或沒有兒子,要麽它有兩個兒子。
先考慮只有一個或沒有兒子的情況,顯然這個點可以直接刪除,將它的子樹接到它的父節點上即可。
對於有兩個兒子的情況,顯然是不能直接刪除這個點的。(因為無法確定左右子所應接在父節點上的位置)
所以對於有兩個兒子的情況,可以通過左旋/右旋將待刪除的點向葉子節點處旋轉,直到只有一個或沒有兒子為止。
在旋轉時,如果改點的左兒子的優先級比右兒子小,就右旋;如果大,就左旋。
void del(int &k, int x) { //刪除
if (k == 0) return; //沒有該節點就直接退出
if (a[k] == x) {
if (c[k] > 1) c[k] --, size[k] --;
else {
if (!lson[k] || !rson[k]) k = lson[k] + rson[k];
else if (pri[lson[k]] < pri[rson[k]]) zag(k), del(k, x);
else zig(k), del(k, x);
}
}
else if (x > a[k]) size[k] --, del(rson[k], x);
else size[k] --, del(lson[k], x);
}
?
?
4、查詢一個數的排名
查詢排名,就是查詢該數是這些數中第幾小的。
查詢排名的方法和二叉排序樹一樣,這裏就不再說了。
int query_rank(int &k, int x) { //查詢排名
if (k == 0) return 2e9;
if (a[k] == x) return size[lson[k]] + 1;
if (x > a[k]) return size[lson[k]] + c[k] + query_rank(rson[k], x);
else return query_rank(lson[k], x);
}
?
?
5、查詢排名第x的數是什麽
即是詢問這些數中從小到大排後第x大的數是什麽。
查詢方法也和二叉排序樹一樣,直接放代碼。
int query_num(int &k, int x) { //查詢排名為x的數
if (k == 0) return 2e9;
if (x <= size[lson[k]]) return query_num(lson[k], x);
x -= size[lson[k]];
if (x <= c[k]) return a[k];
x -= c[k];
return query_num(rson[k], x);
}
?
?
6、查詢數x的前驅
即查詢比x小的最大的數。
和二叉排序樹一樣。如果x比該數大,就往右走;否則往左走。
int query_pre(int &k, int x) { //查詢前驅
if (k == 0) return -2e9;
if (a[k] < x) return max(a[k], query_pre(rson[k], x));
else return query_pre(lson[k], x);
}
?
?
7、查詢數x的後繼
即查詢比x大的最小的數。
也和二叉排序樹一樣。如果x比該數小,就往左走;否則往右走。
int query_next(int &k, int x) { //查詢後繼
if (k == 0) return 2e9;
if (a[k] > x) return min(a[k], query_next(lson[k], x));
else return query_next(rson[k], x);
}
?
?
四、treap模板
放一題treap的模板題。
bzoj3224
代碼如下:
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100005;
int n, cnt, root;
int lson[maxn], rson[maxn], size[maxn], a[maxn], c[maxn], pri[maxn];
int read(void) {
char c; while (c = getchar(), (c < '0' || c > '9') && c != '-'); int x = 0, y = 1;
if (c == '-') y = -1; else x = c - '0';
while (c = getchar(), c >= '0' && c <= '9') x = x * 10 + c - '0'; return x * y;
}
int rand(){
static int seed = 2333;
return seed = (int)((((seed ^ 998244353) + 19260817ll) * 19890604ll) % 1000000007);
}
void up(int k) {
size[k] = size[lson[k]] + size[rson[k]] + c[k];
}
void zig(int &k) { //左旋
int v = rson[k]; rson[k] = lson[v]; lson[v] = k;
size[v] = size[k]; up(k); k = v;
}
void zag(int &k) { //右旋
int v = lson[k]; lson[k] = rson[v]; rson[v] = k;
size[v] = size[k]; up(k); k = v;
}
void insert(int &k, int x) { //插入節點
if (k == 0) { //如果節點數為0,直接插入
k = ++ cnt;
size[k] = c[k] = 1; a[k] = x; pri[k] = rand();
return;
}
size[k] ++;
if (a[k] == x) c[k] ++;
else if (x > a[k]) {
insert(rson[k], x);
if (pri[rson[k]] < pri[k]) zig(k);
}
else {
insert(lson[k], x);
if (pri[lson[k]] < pri[k]) zag(k);
}
}
void del(int &k, int x) { //刪除
if (k == 0) return; //沒有該節點就直接退出
if (a[k] == x) {
if (c[k] > 1) c[k] --, size[k] --;
else {
if (!lson[k] || !rson[k]) k = lson[k] + rson[k];
else if (pri[lson[k]] < pri[rson[k]]) zag(k), del(k, x);
else zig(k), del(k, x);
}
}
else if (x > a[k]) size[k] --, del(rson[k], x);
else size[k] --, del(lson[k], x);
}
int query_rank(int &k, int x) { //查詢排名
if (k == 0) return 2e9;
if (a[k] == x) return size[lson[k]] + 1;
if (x > a[k]) return size[lson[k]] + c[k] + query_rank(rson[k], x);
else return query_rank(lson[k], x);
}
int query_num(int &k, int x) { //查詢排名為x的數
if (k == 0) return 2e9;
if (x <= size[lson[k]]) return query_num(lson[k], x);
x -= size[lson[k]];
if (x <= c[k]) return a[k];
x -= c[k];
return query_num(rson[k], x);
}
int query_pre(int &k, int x) { //查詢前驅
if (k == 0) return -2e9;
if (a[k] < x) return max(a[k], query_pre(rson[k], x));
else return query_pre(lson[k], x);
}
int query_next(int &k, int x) { //查詢後繼
if (k == 0) return 2e9;
if (a[k] > x) return min(a[k], query_next(lson[k], x));
else return query_next(rson[k], x);
}
int main() {
n = read();
while (n --) {
int opt = read(), x = read();
if (opt == 1) insert(root, x);
else if (opt == 2) del(root, x);
else if (opt == 3) printf("%d\n", query_rank(root, x));
else if (opt == 4) printf("%d\n", query_num(root, x));
else if (opt == 5) printf("%d\n", query_pre(root, x));
else if (opt == 6) printf("%d\n", query_next(root, x));
}
return 0;
}
treap詳解