P3369普通平衡树
生活随笔
收集整理的這篇文章主要介紹了
P3369普通平衡树
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
題意
寫一棵平衡樹,需要實現如下幾種操作:
思路
BST
什么是BST(二叉查找樹)呢?
BST是一種二叉樹,用于做一些查找操作(其實很多是題意中所說明的工作)。BST需要滿足當前結點的左子樹中所有結點的鍵值要小于當前結點,而右子樹相反,所有結點的鍵值都要大于當前節點。
BST的性質:
- 一棵BST能對應一個鍵的集合
- 一個集合對應的BST不為一
- 對BST做中序遍歷,得到的序列是嚴格單調遞增的
Treap
Treap是一種編程比較簡單的平衡樹,它在普通二叉搜索樹的基礎上,給每個結點一個隨機的優先級。樹上每個結點在滿足BST的性質以外,還需要根據優先級滿足堆的性質。
旋轉操作
為了要讓Treap同時滿足BST和堆的性質,肯定要對其結構進行調整。這個調整的方式被稱為旋轉。在Treap中,只有兩種旋轉操作——左旋和右旋
左旋一個子樹,會把它的根挪到根的左子樹部分,同時根的右子樹成為子樹的根,再把右子樹的左子樹接到根的右子樹上。
右旋一個子樹,會把它的根轉到右子樹上,同時根的左子樹成為子樹的根。在把左子樹的右子樹接到根的右子樹上。
插入操作
- 如果當前沒有結點,那么就到了位置,直接創建新的。
- 如果當前結點的鍵值為 xxx,那么懶添加,在當前結點的出現次數記錄那里直接 +1+1+1 即可。
- 否則根據BST性質接著遍歷即可。
- 插入后如果不滿足Trep的堆性質了,旋轉。
刪除操作
- 如果不存在,那么返回即可。
- 如果要刪除的元素是在子樹中,去子樹里找
- 如果當前要刪除結點是葉子,那么直接刪除。
- 如果當前點只有一個孩子,那么直接把自己跟孩子換了就行。
- 否則有兩個孩子的話旋轉一下后直接刪除。
查詢x數的排名
- 如果當前節點是x,直接返回左子樹大小 +1+1+1 即可。
- 否則接著安照BST性質遍歷即可。
查詢排名為x的數
- 與上一個操作很像,讀者可以自行想想
前驅后繼
讀者也可以自行想想。
tips
我們為什么不直接用BST呢?可以想想,如果給出一個單調遞增/遞減的序列,是不是都一直往右子樹/左子樹上添加了。一個滿二叉樹(最好情況)的時間復雜度(層數)是 log?n\log{n}logn 的,但是如果都往一個方向,那么層數就變成 nnn 了,這不好。但是如果旋轉,每一次都能減少一層,然后隨機化也會讓這個序列達到一個相對平衡的狀態。所謂的堆性質也可以理解為是一個“莫須有”的罪名罷了。
代碼
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> using namespace std; typedef long long ll; const ll MAXN=1e5+5; const ll INF=2147483647; ll root; struct Node{ll son[2];//0為左孩子ll cnt,value,pri,size;//出現次數,值,優先級,個數 }t[MAXN]; void push_up(ll u){t[u].size=t[t[u].son[0]].size+t[t[u].son[1]].size+t[u].cnt;//為左子樹的大小+右子樹的大小+自身的個數 } void rotate(ll &u,ll d){//d=0為左旋,d=1為右旋ll k=t[u].son[d^1];t[u].son[d^1]=t[k].son[d];t[k].son[d]=u;push_up(u);push_up(k);u=k; } ll sz; void insert(ll &u,ll x){if(u==0){u=++sz;//新元素t[u].cnt=t[u].size=1;//葉子大小和元素個數都為1t[u].value=x;//值為xt[u].pri=rand();//隨機賦優先級return;}if(t[u].value==x) {t[u].size++;//更改空間t[u].cnt++;//多了一個一樣的return;}ll k=x>t[u].value;//在左子樹還是右子樹里insert(t[u].son[k],x);if(t[u].pri<t[t[u].son[k]].pri){//是否滿足堆性質rotate(u,k^1);//旋轉,旋哪個子樹}push_up(u);//更新 } void del(ll &u,ll x){if(u==0){return;//壓根不存在}if(x<t[u].value){del(t[u].son[0],x);//在左子樹里}else if(x>t[u].value){del(t[u].son[1],x);//在右子樹里}else{if(t[u].cnt>1){t[u].size--;//懶刪除t[u].cnt--;//同上}else if(t[u].son[0]==0&&t[u].son[1]==0){u=0;//葉子結點,直接刪除就行了}else if(t[u].son[0]!=0&&t[u].son[1]==0){u=t[u].son[0];//接到左子樹上}else if(t[u].son[0]==0&&t[u].son[1]!=0){u=t[u].son[1];//接到右子樹上}else{ll k=t[t[u].son[0]].pri>t[t[u].son[1]].pri;//兩邊都有,選擇刪rotate(u,k);//旋一下,把根換到底下del(t[u].son[k],x);//接著刪}}push_up(u);//更新 } ll x_rank(ll u,ll x){if(u==0){return 0;//不存在}if(t[u].value==x){return t[t[u].son[0]].size+1;//小的元素個數再+1}if(x<t[u].value){return x_rank(t[u].son[0],x);//比當前小,在左子樹里}return t[t[u].son[0]].size+t[u].cnt+ x_rank(t[u].son[1],x);//左子樹的大小+當前結點元素個數+右子樹中的排名 } ll rank_x(ll u,ll x){if(t[t[u].son[0]].size>=x){return rank_x(t[u].son[0],x);//左子樹的排名比x大}if(t[t[u].son[0]].size+t[u].cnt<x){return rank_x(t[u].son[1],x-t[t[u].son[0]].size-t[u].cnt);//這個結點的排名比x小,去右邊,同時減掉當前排名}return t[u].value;//找到了,返回元素 } ll pre(ll u,ll x){if(u==0){return -INF;//不存在前驅}if(t[u].value>=x){return pre(t[u].son[0],x);//比x大,去左邊}return max(t[u].value, pre(t[u].son[1],x));//是不是當前的,還是在右子樹里,取max } ll suc(ll u,ll x){if(u==0){return INF;//不存在}if(t[u].value<=x){return suc(t[u].son[1],x);//比x小,去右邊}return min(t[u].value, suc(t[u].son[0],x));//選最小的,與前驅相同 } int main(){srand(time(NULL));ll n;scanf("%lld",&n);for (int i = 1; i <=n ; ++i) {ll op,x;scanf("%lld%lld",&op,&x);if(op==1){insert(root,x);}else if(op==2){del(root,x);}else if(op==3){printf("%lld\n",x_rank(root,x));}else if(op==4){printf("%lld\n",rank_x(root,x));}else if(op==5){printf("%lld\n",pre(root,x));}else{printf("%lld\n",suc(root,x));}}return 0; }總結
以上是生活随笔為你收集整理的P3369普通平衡树的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【IDEA 教程系列第 14 篇】ide
- 下一篇: bios卡+型号+hp服务器,HPE G