1. 程式人生 > >Splay初學習

Splay初學習

不存在 查找樹 函數 pri which 覆蓋 存在 想想 查詢

例題傳送門

聽YZ哥哥說Splay是一種很神奇的數據結構,所以學習了一下它的最基本操作。O(1)的Spaly。

伸展樹(Splay Tree),也叫分裂樹,是一種二叉排序樹,它能在O(logn)內完成插入、查找和刪除操作。它由丹尼爾·斯立特Daniel Sleator和羅伯特·恩卓·塔揚Robert Endre Tarjan在1985年發明的。 在伸展樹上的一般操作都基於伸展操作:假設想要對一個二叉查找樹執行一系列的查找操作,為了使整個查找時間更小,被查頻率高的那些條目就應當經常處於靠近樹根的位置。於是想到設計一個簡單方法, 在每次查找之後對樹進行重構,把被查找的條目搬移到離樹根近一些的地方。伸展樹應運而生。伸展樹是一種自調整形式的二叉查找樹,它會沿著從某個節點到樹根之間的路徑,通過一系列的旋轉把這個節點搬移到樹根去。
它的優勢在於不需要記錄用於平衡樹的冗余信息。
Splay是一種自調整數據結構,它的核心是Splay操作。 Splay操作是Rotate的升級版,該函數將子節點旋轉到根,來保持Splay的復雜度。 //我們這裏講的是雙旋Splay,之所以稱為雙旋,是因為在判斷父子關系之前,要旋一次,共旋兩次。 Splay操作要分兩種情況討論。 ①當前要Splay的點和它的父親及其父親的父親(如果它有父親的父親的話)在同一直線上,先旋它的父親。 ②當前要Splay的點和它的父親及其父親的父親(如果它有父親的父親的話)不在在同一直線上,旋該節點。 插入:在插入節點後,記錄插入節點的位置,Splay到根。 刪除:先找到要刪的節點,記錄下來,沒有就return。刪除要分幾種情況討論;
①沒有左右節點,直接clear根。
   ②只有左節點或右節點,用左或右節點將根覆蓋,clear原來的根。    ③既有左節點又有右節點,將root的前驅Splay到根,然後將root的右節點(原來的root)的右節點連接到root的右節點上就好了。 其他操作跟Treap相似。 code:
#include <cstdio>
#include <cstring>
using namespace std;

int read()
{
    char c;while(c=getchar(),(c<0||c>9)&&c!=
-); int x=0,y=1;c==-?y=-1:x=c-0; while(c=getchar(),c>=0&&c<=9)x=x*10+c-0; return x*y; } int N,dist; struct Splay{ int tr[100005][2],v[100005],tot[100005]; int f[100005],fa[100005],root,cnt; Splay(){ memset(tr,0,sizeof tr); memset(v,0,sizeof v); memset(tot,0,sizeof tot); memset(f,0,sizeof f); cnt=root=0; }//初始化 void clear(int x){tr[x][0]=tr[x][1]=f[x]=fa[x]=tot[x]=v[x]=0;}//清除節點信息 void up(int x){f[x]=f[tr[x][0]]+f[tr[x][1]]+tot[x];}//更新節點信息 int get(int x){return tr[fa[x]][1]==x;}//which son of father void rotate(int &x) { int ol=fa[x],olol=fa[ol],to=get(x); tr[ol][to]=tr[x][to^1];fa[tr[x][to^1]]=ol; fa[ol]=x;tr[x][to^1]=ol; fa[x]=olol; if(olol)//如果該節點的父親的父親存在 tr[olol][tr[olol][1]==ol]=x; up(ol);up(x);//這裏一定要先更新ol,在更新x,想想為什麽 } void splay(int x) { for(int S;S=fa[x];rotate(x))//先旋x if(fa[S])//S不為根 rotate((get(x)==get(S)?S:x));//判斷是否三點一線 root=x; } void insert(int &x,int val,int pos) { if(!x){//插入 x=++cnt; f[x]=tot[x]=1,v[x]=val,fa[x]=pos; dist=x;//記錄節點 return ; } if(val==v[x]){dist=x,tot[x]++;return ;}//有一樣的數 insert(tr[x][val>v[x]],val,x); up(x);//更新 return ; } int QueryX(int x,int val)//查詢x的排名 { if(!x)return 0; if(val==v[x])return f[tr[x][0]]+1; int to=val>v[x]; return QueryX(tr[x][to],val)+(to?f[tr[x][0]]+tot[x]:0); } int QueryK(int x,int kth)//查詢排名為K的數 { if(!x)return 0; if(kth<=f[tr[x][0]])return QueryK(tr[x][0],kth); if(kth>f[tr[x][0]]+tot[x])return QueryK(tr[x][1],kth-(f[tr[x][0]]+tot[x])); return v[x]; } void pre(int x,int val)//前驅 { if(!x)return ; if(val<=v[x])return pre(tr[x][0],val); else dist=x,pre(tr[x][1],val); } void bac(int x,int val)//後繼 { if(!x)return ; if(v[x]<=val)bac(tr[x][1],val); else dist=x,bac(tr[x][0],val); } int find(int x,int val)//在del之前先找節點,記錄,並Splay { if(!x)return 0; if(v[x]==val){splay(x);return x;}//找到 int to=val>v[x]; return find(tr[x][to],val); } void del(int x)//刪除 { int kkk=find(root,x); if(!kkk)return ;//不存在 if(tot[root]>1){ tot[root]--,up(root); return ; } if(!tr[root][0]&&!tr[root][1]){ clear(root),root=0; return ; } if(!(tr[root][0]*tr[root][1])){ int rt=root; root=tr[root][0]+tr[root][1]; fa[root]=0,clear(rt);//細節是要先記錄root,再更新root,再將原來的root clear return ; } int ok=root;//原來的root pre(root,x);//求前驅 splay(dist);//Splay到根 fa[tr[ok][1]]=root; tr[root][1]=tr[ok][1]; clear(ok);up(root);fa[root]=0;//clear,更新,細節是要把新根的fa清零 return ; } }W; int main() { N=read(); while(N--){ int o=read(),x; switch(o){ case 1:W.insert(W.root,read(),0);W.splay(dist);break; case 2:W.del(read());break; case 3:printf("%d\n",W.QueryX(W.root,read()));break; case 4:printf("%d\n",W.QueryK(W.root,read()));break; case 5:dist=0,W.pre(W.root,read());printf("%d\n",W.v[dist]);break; case 6:dist=0,W.bac(W.root,read());printf("%d\n",W.v[dist]);break; } } }

Splay初學習