於是來講一講這個噁心的東西吧……(全程不用指針,請做好心理準備……)
學習前請先學習下二叉搜索樹,裏面可能直接用到這個東西
首先,Splay是一個數據結構,爲了突出它是一個數據結構,所以給他開個結構體……
struct tree
{
int val,sz,cnt;//val爲值 sz爲子樹大小,cnt爲"有多少這個值"
//cnt:假如出現了兩個一模一樣的值,只需要讓cnt+1就可以了,cnt是數目
int s[2],f;//s[0]爲左兒子 s[1]爲右兒子 f爲父親
};
爲了之後的操作,我們再寫三個函數(如果認爲沒用可以先跳過,過一會用到了再回來看)
bool son(int x) //返回x是左兒子還是右兒子,如果爲左兒子,返回0,右兒子則返回1
{
return a[a[x].f].s[1] == x;
}
void rejs(int x) //重新計算以x爲根,子樹的大小,子樹大小等於左子樹大小加右子樹大小(好吧我承認我英語拙計)
{
a[x].sz = a[a[x].s[0]].sz + a[a[x].s[1]].sz + a[x].cnt;
}
void point(int x,int y,bool z) //在x下面插入y,y是x的z兒子(z爲0則左兒子,z爲1爲右兒子)
{
a[x].s[z] = y;
a[y].f = x;
}
寫完這幾個樹的基本函數,我們要開始講splay啦(似乎我之前好像真的沒有開始講splay)
Splay最重要的一個操作就是旋轉(rotate),如下,(圖片爲1600x900,可直接作爲桌面):
我們的目的是將紅色節點旋轉到它父親的位置,即黃色節點的位置
我們應當怎麼辦呢?不要急,看組圖
bool型變量son(黃)代表黃色節點是左兒子還是右兒子
將紅色節點接到黃色節點的父親(也就是綠色節點)的下面,是綠色節點的son(黃)兒子,也就是說代替了黃色節點;
黃色節點被代替了怎麼辦?再開個變量記錄一下即可
bool型變量son(紅)代表紅色節點是左兒子還是右兒子
把紅色節點的son(紅)&1兒子(圖中的藍色節點,藍色節點和父親關係和紅色節點和父親關係正好相反)接到黃色節點下面,是黃色節點是son(紅)兒子,代替了紅色節點;
最後把黃色節點的父親改爲紅色節點,旋轉完成!
旋轉完成後:
(我再也不用PS畫這玩意了……)
void rot(int x)
{
int p = a[x].f;
bool d = son(x);
point(a[p].f,x,son(p));
point(p,a[x].s[d^1],d);
point(x,p,d^1);
rejs(p);
rejs(x);
if(a[x].f == 0)
root = x;//root是根節點的編號
}
這就是Splay的核心操作——rotate,它的時間複雜度是——O(1)
然而學會了有什麼用呢= =,下面來講Splay的其他操作及如何平衡
首先是Splay的插入操作,Splay每插入一個新節點,就把這個節點強制旋轉到根節點,如何強制旋轉到根節點呢?如果while(不是根節點)rotate(x)的話就有可能退化成一條鏈……於是Splay“貼心”的爲我們準備了splay操作……
splay操作的目的是將一個點旋轉到根節點,這個操作是這樣的:
1.如果這個點是根節點,那麼你可以直接退出了……
2.如果這個點的父親是根節點,那麼就直接把這個點rotate上去……
3.如果上述兩條均不滿足,那麼分類討論:
(1)設son(x)爲這個和父親節點的關係,son(f)爲父親節點和爺爺節點的關係
(2)如果兩個關係相同(均爲左兒子或均爲右兒子),那麼就先rotate父親節點,然後rotate這個節點
(3)如果兩個關係不同(一個是左兒子一個是右兒子),那麼就把這個節點連續rotate兩次
void splay(int x)
{
while(a[x].f != 0){
if(a[a[x].f].f == 0)
rot(x);
else
{
if(son(x) == son(a[x].f)){
rot(a[x].f);
rot(x);
}
else{
rot(x);
rot(x);
}
}
}
}
這樣就可以寫出插入操作了,還記得插入操作怎麼做嗎?插入一個節點然後splay到根節點
void ins(int x)
{
int w = root,f = 0;
int p = findn(x);
if(p)//如果這個值已經存在,那麼就直接給這個節點+1吧
{
a[p].cnt ++;
while(p != root)
{
rejs(p);
p = a[p].f;
}
rejs(root);
return ;
}
while(w)
{
f = w;
if(x < a[w].val)
w = a[w].s[0];
else
w = a[w].s[1];
}
//這是前半部分,和普通的二叉查找樹插入方法一樣,我承認我打的很醜……
a[++tot].val = x;
a[tot].cnt = 1;//新建節點,標號爲tot
if(f == 0)//如果這個點是根節點的話,直接插入即可……
{
root = tot;
rejs(root);
return ;
}
//否則插入這個節點並spaly到根節點
if(x < a[f].val)
point(f,tot,0);
else
point(f,tot,1);
splay(tot);
}
然後是刪除操作,Splay的刪除操作比較喪病,我只講一下我的寫法吧…… 設要刪除的節點爲x,那麼,首先splay一下x的前驅,然後splay一下x的後繼,啥?你前驅和後繼還不會寫???好吧好吧,我講……
x的前驅是指小於x且最大的節點,後繼就是大於x且最小的節點
前驅就是在x的左子樹上一直往右跑,如果沒有左子樹那就往上找
後繼就是在x的右子樹上一直往左跑,如果沒有右子樹那也往上找
int near(int x,bool d)
{
if(a[x].s[d] != 0){
int p = a[x].s[d];
while(a[p].s[d^1])
p = a[p].s[d^1];
return p;
}
else if(son(x) == d^1)
return a[x].f;
else{
int p = a[x].f;
while(son(p) == d){
if(p == root)
return 0;
p = a[p].f;
}
return a[p].f;
}
}
接着繼續我們的刪除操作,刪除操作就是上面講的:splay(x的前驅),splay(x的後繼),然後x的後繼變爲根節點,x的前驅變爲根節點的左兒子,x到哪裏了呢? x成爲了x前驅的右子樹!直接砍掉這個右子樹就ok啦~(如果刪除區間也可以這樣做,splay區間左邊界的前驅,splay區間右邊界的後繼,整個區間就變成了區間左界前驅的右子樹~(≧▽≦)/~)
void cle(int x)//cle操作是清除一個點,沒有也無所謂吧
{
a[a[x].f].s[son(x)] = 0;
a[x].val = 0;
a[x].sz = 0;
rejs(a[x].f);
a[x].f = 0;
}
void del(int x)
{
int p = near(x,0);
int q = near(x,1);
if(!p || !q)
{
splay(x);
if(p == 0)
{
root = a[x].s[1];
a[a[x].s[0]].f = 0;
}
else
{
root = a[x].s[0];
a[a[x].s[0]].f = 0;
}
return ;
}
splay(p);
splay(q);
rot(p);
cle(x);
}
至此你終於獲得了一棵可用的splay,拿去亂搞吧!
轉自:http://blog.csdn.net/farestorm/article/details/49153565