rt
totfa[i]
chival[i]
cnt[i]
size[i]
根節點編號
節點個數
父親左右兒子編號
節點權值
權值出現次數
子樹大小
maintain(x):在改變節點位置後,將節點x的size更新;get(x):判斷節點x是父親節點的左孩子還是右孩子;
clear(x):銷毀節點x;
void maintain(int x)
bool get(int x)
void clear(int x)
為了使得splay保持平衡而進行旋轉操作,旋轉的本質就是將某個節點上移乙個位置。
旋轉需要保證:
1.整顆樹splay中序遍歷不變(不破壞二叉查詢樹的性質);
2.受影響節點維護資訊依然正確有效;
3.root必須指向旋轉後的根節點;
splay中分兩種旋**左旋和右旋
1 2
/ \ / \
2 3 --->右旋 4 1
/ \ <---左旋 / \
4 5 5 3
具體分析旋轉步驟:
假設需要旋轉的節點為x,其父親為y,以右旋為例;
1.將y的左孩子指向x的右孩子,且x的右孩子的父親指向y;
ch[y][0] = ch[x][1];
fa[ch[x][1]] = y;
2.將x的右孩子指向y,且y的父親指向x;
ch[x][chk^1] = y;
fa[y] = x;
3.如果原來y還有父親z,那麼把z的某個兒子(原來y所在的位置)指向x,且x的父親指向z;
fa[x] = z;
if (z)
void rotate(int x)
splay規定:每訪問乙個節點後都要強制將其旋轉到根節點,此時旋轉操作具體分6種情況討論(其中x為需要旋轉到根的節點)。
y
/
x
y
\
x
z
/
y
/
x
z
\
y
\
x
z
\
y
/
x
z
/
y
\
x
1.如果x的父親是根節點,直接旋轉x左旋或右旋;(1/2)
2.如果x的父親不是根節點,且x和父親的兒子型別相同,首先將其父親左旋或右旋,然後再將x旋轉;(3/4)
3.如果x的父親不是根節點,且x和父親的兒子型別不相同,將x左旋在右旋,或者右旋在左旋;(5/6)
void splay(int x)
rt = x;
}
插入操作是乙個比較複雜的過程,具體步驟如下(插入值為k):
1.如果樹空,直接插入退出;
2.如果當前節點權值等於k則增加當前節點的大小並更新節點和父親的資訊,將當前節點進行splay操作;
3.否則按照二叉樹性質往下找,找到空節點插入即可,最後splay一下;
void insert(int k)
int cnr = rt, f = 0;
while (1)
f = cnr;
cnr = ch[cnr][val[cnr] < k];
if (!cnr)
}}
根據二叉樹的定義和性質,顯然可以按照以下步驟查詢x的排名:
1.如果x比當前節點的權值小,向其左子樹查詢;
2.如果x比當前節點的權值大,將其加上左子樹和當前節點cnt大小,向右子樹查詢;
3.如果x與當前節點的權值相同,將答案加1並返回;
int rank(int k)
else
res += cnt[cnr];
cnr = ch[cnr][1];}}
}
設k為剩餘排名,步驟如下:
1.如果左子樹非空且剩餘排名k不大於左子樹的size,那麼向左子樹查詢;
2.否則將k減去左子樹和根的大小,如果此時k的值小於等於0,則返回根節點的權值,否則繼續向右子樹查詢;
inr kth(int k)
else
cnr = ch[cnr][1];}}
}
前驅定義為小於x的最大數,那麼查詢前驅就可以變為:將x插入(此時x已經在根節點上),前驅即為x的左子樹中最右邊的節點,最後將x刪除即可;
int pre(int x)
後繼定義為大於x的最小的數,查詢和前驅類似:x的右子樹中的最左邊節點;
int next()
合併兩顆splay樹,設兩顆樹的根節點分別為x和y,先做著假定,要求x樹種最大值不大於y中的最小值。合併操作如下:
1.如果x和y其中之一或兩者都為空樹,直接返回不為空的那一顆樹的根節點或空樹;
2.否則將x樹中的最大值splay到根節點,然後將他的右子樹設定為y並更新節點資訊,然後返回這個節點;
int merge(int x, int y)
if (!y)
//找到x樹種最大值;
int cnr = ch[x][1];
while (ch[cnr][1]) cnr = ch[cnr][1];
splay(cnr);
fa[y] = cnr;
ch[cnr][1] = y;
return cnr;
}
刪除操作具體如下:
1.首先將x旋轉到根節點;
2.如果cnt[x] > 1,將cnt[x]--後退出;
3.否則,合併它的左右兩顆子樹即可;
void del(int k)
//cnt[k]==1;
if (!ch[rt][0] && !ch[rt][1])
if (!ch[rt][0])
if (!ch[rt][1])
int cnr = rt, x = pre();
splay(x);
fa[ch[cnr][1]] = x;
ch[x][1] = ch[cnr][1];
clear(cnr);
maintain(cnr);
return;
}
#include const int n = 100005;
int rt, tot, fa[n], ch[n][2], val[n], cnt[n], sz[n];
struct splay
bool get(int x)
void clear(int x)
void rotate(int x)
void splay(int x)
void ins(int k)
int cnr = rt, f = 0;
while (1)
f = cnr;
cnr = ch[cnr][val[cnr] < k];
if (!cnr)
}} int rk(int k) else
res += cnt[cnr];
cnr = ch[cnr][1];}}
} int kth(int k) else
cnr = ch[cnr][1];}}
} int pre()
int nxt()
void del(int k)
if (!ch[rt][0] && !ch[rt][1])
if (!ch[rt][0])
if (!ch[rt][1])
int cnr = rt;
int x = pre();
splay(x);
fa[ch[cnr][1]] = x;
ch[x][1] = ch[cnr][1];
clear(cnr);
maintain(rt);
}} tree;
int main()
return 0;
}
//參考**:
為了加深理解,手抄一遍;
平衡樹之splay
在每次查詢之後對樹進行重構 把被查詢的條目搬移到 離樹根近一些的地方。伸展樹應運而生。伸展樹是一種自調整形式的二叉查詢樹,它會 沿著從某個節點到樹根之間的路徑 通過一系列的旋轉把這個節點搬移到樹根去。大家只需要記住,每次進行插入 查詢的時候,都要把插入 查詢的元素通過旋轉變到根的位置,splay 的...
(模板)Splay 平衡樹
不會講解,直接上板子,按照洛谷p3369的要求 include include include using namespace std const int maxn 1000000 int ch maxn 2 f maxn size maxn cnt maxn key maxn int nodecn...
平衡樹 Splay 模板
又是 機房最後乙個學spl ay splay splay的人 參考d al ao dalao dalao部落格 寫的非常好!最後放上我的 有個地方應該寫ch root 0 ch root 0 ch roo t 0 手殘寫成ch root 1 ch root 1 ch roo t 1 還查不出來t t...