平衡樹一 Splay樹

2022-08-22 13:09:09 字數 4669 閱讀 9037

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...