Treap(旋轉) bzoj3224普通平衡樹
Treap = Tree + heap,Tree是BST,即同時維護二叉查詢樹和堆的性質
Treap的定義:
int ch[maxn][2], val[maxn], siz[maxn], num[maxn], pri[maxn];//此處優先順序用了小根堆
int tot, rt;
#define ls ch[now][0]
#define rs ch[now][1]
ch陣列存的是左右孩子節點的下標
val是BST的值,siz是子樹大小,
num指值為val的數有多少個
pri是堆的值(此處選擇了小根堆)
tot是treap的結點總數(val相同的很多個數只佔一個結點)
rt是當前的樹根
巨集是拿來偷懶滴
1、一棵BST,無論如何左旋右旋,結點間的關係一定不會變。
2、BST在資料隨機的情況下所有操作的期望都是O(logN)
Treap就是利用以上兩點,把插入的資料通過旋轉,變得好像是隨機插入的一樣。
Treap的每次插入,先像普通的BST一樣找到插入的位置,然後給pri賦隨機值,再以旋轉的方式用pri在不改變BST的性質的前提下維護heap。
旋轉:
旋轉後siz會變,要更新
inline void pushup(int now)
{
siz[now] = siz[ls] + siz[rs] + num[now];
}
void rotate(int &x,int dir)//把now旋轉到它的兒子的位置,dir為0代表右旋,1左旋
{
int son = ch[x][dir];
ch[x][dir] = ch[son][dir^1];
ch[son][dir^1] = x;
pushup(x);//旋轉後x在下面,先更新x
pushup(x=son);
}
插入一個值為v的數:
遞迴寫起來比較舒服~
4種情況:
1、找到了一個空位
新建一個點,over
2、當前節點的val == v
當前結點的num+1,over
3、當前節點的val > v
遞迴往左子樹插入
檢查左孩子的pri是否小於當前的pri,小於則右旋(左子樹完成插入後可能不滿足堆的性質)
4、跟3反過來
一路上的siz都要++(下面多了一個數)
void Insert(int v,int &now)
{
if(!now)//找到空位直接新開一個點
{
newnode(v,now);
return ;
}
++siz[now];
if(val[now] == v)//這個數已經存在,num+1即可
{
++num[now];
return ;
}
else if(v < val[now])
{
Insert(v,ls);
if (pri[ls] < pri[now])
rotate(now,0);//左兒子優先順序小就把左兒子旋到上面來(右旋)
}
else
{
Insert(v,rs);
if(pri[rs] < pri[now])
rotate(now,1);//右兒子優先順序小就把右兒子旋到上面來(左旋)
}
}
刪除一個值為v的數:
3種情況 吧
1、如果找到val == v的結點,且該結點的num > 1,num-1就行了
2、找到val == v的結點,且該結點的num == 1
(1)如果沒有左右子樹,直接賦個0刪了就行
(2)如果左右子樹中有一個為空,賦值為不為空的那個(就是直接拿孩子把它覆蓋掉)
(3)如果左右子樹都有,把孩子中pri小的那個轉上來(維護堆的性質不變),然後再遞迴刪當前結點(為什麼不是刪孩子結點?因為旋轉完,當前節點已經轉到孩子那裡去了啊)
3、val != v
遞迴刪左或右即可
一路上的siz都要-1
3的(3)siz不用-1,因為轉上去的孩子siz都是沒變的,轉下去的那個點被刪了,不用管它的siz
void dele(int v,int &now)
{
if(!now)//防非法資料
return ;
if(val[now] == v)
{
if(num[now] > 1)
{
--siz[now];
--num[now];
return ;
}
else
{
if(!ls && !rs)
{
now = 0;
return ;
}
else if(!ls || !rs)
{
now = ls + rs;
return ;
}
else
{
int dir = pri[ls] > pri[rs];
rotate(now,dir);
dele(v,now);
}
}
}
else
{
--siz[now];
dele(v,ch[now][v>val[now]]);
}
}
查值為v的數的排名
比較簡單 ,直接看註釋吧
int rk(int v,int now)
{
if(!now) return 0;//防非法資料
if(v == val[now])
return siz[ls] + 1;//siz[ls]是比v小的數的數量,+1就是v的排名
else if(v < val[now])
return rk(v,ls);
else
return rk(v,rs)+siz[ls]+num[now];//往右子樹找記得把左子樹大小和當前點的大小加上
}
查排名為k的數:
跟上面差不多
int kth(int k,int now)
{
if(siz[ls] < k && siz[ls] + num[now] >= k)
return val[now];
else if(k <= siz[ls])
return kth(k,ls);
else
return kth(k-siz[ls]-num[now],rs);
}
求前驅、後繼
int pre(int v,int now)
{
if(!now) return -inf;//找到空的地方返回-inf,因為上一層有max,-inf不會計入答案
if(val[now] >= v)//當前節點的值比v大,不可能成為前驅
return pre(v,ls);//往左節點找
return max(pre(v,rs), val[now]);
}
int suc(int v,int now)
{
if(!now) return inf;
if(val[now] <= v)//當前節點的值比v小,不可能成為後繼
return suc(v,rs);//往右節點找
return min(suc(v,ls), val[now]);
}
bzoj3224普通平衡樹:
#include<cstdio>
#include<queue>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int maxn = 1e5 + 5;
const int inf = 1e9 + 9;
int ch[maxn][2], val[maxn], siz[maxn], num[maxn], pri[maxn];//優先順序用小根堆
int tot, rt;
#define ls ch[now][0]
#define rs ch[now][1]
queue<int> que;
inline void newnode(int v,int &x)
{
if(!que.empty())
{
x = que.front();
que.pop();
}
else
x = ++tot;
ch[x][0] = ch[x][1] = 0;
num[x] = 1;
val[x] = v;
siz[x] = 1;
pri[x] = rand();
}
inline void reuse(int x)
{
que.push(x);
}
inline void pushup(int now)
{
siz[now] = siz[ls] + siz[rs] + num[now];
}
void rotate(int &x,int dir)//把now旋轉到它的兒子的位置,dir為0代表左兒子
{
int son = ch[x][dir];
ch[x][dir] = ch[son][dir^1];
ch[son][dir^1] = x;
pushup(x);//旋轉後x在下面,先更新x
//x = son;
pushup(x=son);
}
void Insert(int v,int &now)
{
if(!now)//找到空位直接新開一個點
{
newnode(v,now);
return ;
}
++siz[now];
if(val[now] == v)//這個數已經存在,num+1即可
{
++num[now];
return ;
}
else if(v < val[now])
{
Insert(v,ls);
if(pri[ls] < pri[now])
rotate(now,0);//左兒子優先順序小就把左兒子旋到上面來(右旋)
}
else
{
Insert(v,rs);
if(pri[rs] < pri[now])
rotate(now,1);//右兒子優先順序小就把右兒子旋到上面來(左旋)
}
}
void dele(int v,int &now)
{
if(!now)
return ;
if(val[now] == v)
{
if(num[now] > 1)
{
--siz[now];
--num[now];
return ;
}
else
{
if(!ls && !rs)
{
reuse(now);
now = 0;
return ;
}
else if(!ls || !rs)
{
reuse(now);
now = ls + rs;
return ;
}
else
{
int dir = pri[ls] > pri[rs];
rotate(now,dir);
dele(v,now);
}
}
}
else
{
--siz[now];
dele(v,ch[now][v>val[now]]);
}
}
int rk(int v,int now)
{
if(!now) return 0;
if(v == val[now])
return siz[ls] + 1;
else if(v < val[now])
return rk(v,ls);
else
return rk(v,rs)+siz[ls]+num[now];
}
int kth(int k,int now)
{
if(siz[ls] < k && siz[ls] + num[now] >= k)
return val[now];
else if(k <= siz[ls])
return kth(k,ls);
else
return kth(k-siz[ls]-num[now],rs);
}
int pre(int v,int now)
{
if(!now) return -inf;
if(val[now] >= v)//當前節點的值比v大,不可能成為前驅
return pre(v,ls);//往左節點找
return max(pre(v,rs), val[now]);
}
int suc(int v,int now)
{
if(!now) return inf;
if(val[now] <= v)//當前節點的值比v小,不可能成為後繼
return suc(v,rs);//往右節點找
return min(suc(v,ls), val[now]);
}
int n,op,x;
int main()
{
scanf("%d",&n);
while(n--)
{
scanf("%d%d",&op,&x);
switch(op)
{
case 1: Insert(x,rt); break;
case 2: dele(x,rt); break;
case 3: printf("%d\n",rk(x,rt));break;
case 4: printf("%d\n",kth(x,rt));break;
case 5: printf("%d\n",pre(x,rt));break;
default:printf("%d\n",suc(x,rt));
}
}
return 0;
}