平衡樹之Treap
阿新 • • 發佈:2020-07-21
二叉搜尋樹
性質:一個節點x左子樹所有點的關鍵字都比x的關鍵字小,右子樹所有點的關鍵字都比x的關鍵字大
treap
“樹堆” “Tree+Heap”
性質:每個點隨機分配一個權值,使treap同時滿足堆性質和二叉搜尋樹性質
複雜度:期望O(logn)
設每個節點的關鍵字是key,隨機權值是rand
如果v是u的左兒子,則key[v]<key[u]
如果v是u的右兒子,則key[v]>key[u]
如果v是u的子節點,則rand[u]>rand[v]
treap維護權值的時候一般會把相同的權值放在同一個節點上
所以一個treap節點需要維護以下資訊:
1.左右兒子
2.關鍵字
3.關鍵字出現次數
4.堆隨機值
5.節點大小(即子樹大小)
旋轉
平衡二叉搜尋樹主要通過旋轉來保持樹的平衡,即保證複雜度
旋轉有單旋和雙旋,treap只需要單旋,這一點比較簡單
void lturn(int &k)//treap的旋轉 { int t=tr[k].r; tr[k].r=tr[t].l; tr[t].l=k; tr[t].size=tr[k].size; update(k); k=t; } void rturn(int &k) { int t=tr[k].l; tr[k].l=tr[t].r; tr[t].r=k; tr[t].size=tr[k].size; update(k); k=t; }
treap的插入
先給這個節點分配一個隨機的堆權值
然後把這個節點按照BST的規則插入到一個葉子上:
從根節點開始,逐個判斷當前節點的值與插入值的大小關係。如果插入值小於當前節點值,則遞迴至左兒子;大於則遞迴至右兒子;
然後通過旋轉來調整,使得treap滿足堆性質
void insert(int &k,int x)//treap的插入 { if(!k) { size++; k=size; tr[k].size=tr[k].w=1; tr[k].v=x; tr[k].rnd=rand(); return ; } tr[k].size++; if(tr[k].v==x) tr[k].w++; else if(x>tr[k].v) { insert(tr[k].r,x); if(tr[tr[k].r].rnd<tr[k].rnd) lturn(k); } else { insert(tr[k].l,x); if(tr[tr[k].l].rnd<tr[k].rnd) rturn(k); } }
treap的刪除
和普通的BST刪除一樣:
如果插入值小於當前節點值,則遞迴至左兒子;大於則遞迴至右兒子
若當前節點數值的出現次數大於1,則減一(通常將同一個權值縮掉)
若當前節點數值的出現次數等於1:
若當前節點沒有左兒子與右兒子,則直接刪除該節點(置為0);
若當前節點沒有左兒子或右兒子,則將左兒子或右兒子替代該節點
若當前節點有左兒子與右兒子,則不斷旋轉當前節點,並走到當前節點新的對應位置,直到沒有左兒子和右兒子為止。
void del(int &k,int x)//treap的刪除
{
if(!k)
return;
if(tr[k]==x)
{
if(tr[k].w>1)//若不止相同值的個數有多個,刪去一個
{
tr[k].w--;
tr[k].size--;
return ;
}
if(tr[k].l*tr[k].r==0)//有一個兒子為空
k=tr[k].l+tr[k].r;
else if(tr[tr[k].l].rnd<tr[tr[k].r].rnd)
rturn(k),del(k,x);
else
lturn(k),del(k,x);
}
else if(x>tr[k].v)
tr[k].size--,del(tr[k].r,x);
else
tr[k].size--,del(tr[k].l,x);
}
treap的查詢
遞迴到葉子節點,一路維護資訊即可
int query_rank(int k,int x)//treap的查詢
{
if(!k)
return 0;
if(tr[k].v==x)
return tr[tr[k].l].size+1;
else if(x>tr[k].v)
return tr[tr[k].l].size+tr[k].w+query_rank(tr[k].r+x);
else
return query_rank(tr[k].l,x);
}
int query_num(int k,int x)
{
if(!k)
return 0;
if(x<=tr[tr[k].l].size)
return query_num(tr[k].l,x);
else if(x>tr[tr[k].l].size+tr[k].w)
return query_num(tr[k].r,x-tr[tr[k].l].size-tr[k].w);
else
return tr[k].v;
}
treap還可以支援維護序列時的分裂合併,這裡不詳細講了
#include<iostream>
#include<cstdio>
struct data()
{
int l;
int r;
int v;
int size;
int rnd;
int w;
}tr[100005];
int n,size,root,ans;
void update(int k)//更新節點資訊
{
tr[k].size=tr[tr[k].l].size+tr[tr[k].r].size+tr[k].w;
}
void lturn(int &k)//treap的旋轉
{
int t=tr[k].r;
tr[k].r=tr[t].l;
tr[t].l=k;
tr[t].size=tr[k].size;
update(k);
k=t;
}
void rturn(int &k)
{
int t=tr[k].l;
tr[k].l=tr[t].r;
tr[t].r=k;
tr[t].size=tr[k].size;
update(k);
k=t;
}
void insert(int &k,int x)//treap的插入
{
if(!k)
{
size++;
k=size;
tr[k].size=tr[k].w=1;
tr[k].v=x;
tr[k].rnd=rand();
return ;
}
tr[k].size++;
if(tr[k].v==x)
tr[k].w++;
else if(x>tr[k].v)
{
insert(tr[k].r,x);
if(tr[tr[k].r].rnd<tr[k].rnd)
lturn(k);
}
else
{
insert(tr[k].l,x);
if(tr[tr[k].l].rnd<tr[k].rnd)
rturn(k);
}
}
void del(int &k,int x)//treap的刪除
{
if(!k)
return;
if(tr[k]==x)
{
if(tr[k].w>1)//若不止相同值的個數有多個,刪去一個
{
tr[k].w--;
tr[k].size--;
return ;
}
if(tr[k].l*tr[k].r==0)//有一個兒子為空
k=tr[k].l+tr[k].r;
else if(tr[tr[k].l].rnd<tr[tr[k].r].rnd)
rturn(k),del(k,x);
else
lturn(k),del(k,x);
}
else if(x>tr[k].v)
tr[k].size--,del(tr[k].r,x);
else
tr[k].size--,del(tr[k].l,x);
}
int query_rank(int k,int x)//treap的查詢
{
if(!k)
return 0;
if(tr[k].v==x)
return tr[tr[k].l].size+1;
else if(x>tr[k].v)
return tr[tr[k].l].size+tr[k].w+query_rank(tr[k].r+x);
else
return query_rank(tr[k].l,x);
}
int query_num(int k,int x)
{
if(!k)
return 0;
if(x<=tr[tr[k].l].size)
return query_num(tr[k].l,x);
else if(x>tr[tr[k].l].size+tr[k].w)
return query_num(tr[k].r,x-tr[tr[k].l].size-tr[k].w);
else
return tr[k].v;
}