初涉平衡樹 替罪羊樹

2022-05-20 03:57:06 字數 4216 閱讀 6744

「替罪羊樹」:乙個看上去很玄學的名字

「替罪羊」這個名字非常有趣(以至於一開始我並不覺得這是什麼好懂的東西)。名字的**大概是由於它在刪除時候需要用被刪除節點的:左子樹最後乙個節點/右子樹第乙個節點來頂替這個節點。資料結構圈居然還有這麼腦洞的名字(好像還有乙個東西叫做朝鮮樹來著?)

替罪羊的精華在於,它相較於其他大多數的平衡樹而言,並不需要旋轉操作。它維護平衡的方式是非常暴力美學的**點並重構

我們先來考慮一下兩顆樹在內容相同的情況下,怎麼樣的才算是更加「平衡」。顯而易見,形象的來說,更接近於滿二叉樹的二叉搜尋樹更加平衡。

那麼什麼時候兩顆二叉搜尋樹會內容相同呢?

注意到二叉搜尋樹的性質:任意乙個節點記為$a$,其左兒子記為$l$,右兒子記為$r$,那麼必有$l_≤a_≤r_$。也就是說,一顆二叉搜尋樹的中序遍歷的權值是單調的。這也說明若兩顆二叉搜尋樹內容相同,當且僅當其中序遍歷相同。

於是我們發現可以先把這顆不太平衡的二叉搜尋樹壓成中序遍歷後的序列,然後再由這個序列重新構造出乙個內容相同的更加平衡的二叉搜尋樹。

這個過程就叫做「拍扁」

1

void hit(intx)2

9int build(int l, int

r)10

20 inline void rebuild(int

x)21

那麼重構這麼做就好了。

我們現在知道了如何重構,但是何時重構呢?

最樸素的想法就是制定乙個閾值,當插入次數超過閾值時就重構一遍。

這種方法並不需要任何思維難度,但是缺點也顯而易見:遇上特定資料時效率會大打折扣(luogu4169 [violet]天使玩偶/sjy擺棋子  的point#11就是乙個卡替罪羊樹非子樹重構的資料)。

回到替罪羊的特點:它維護平衡的操作是不依靠旋轉的。

這就意味著,替罪羊樹不一定每時每刻都那麼平衡。但是它相較於需要旋轉的平衡樹來說,節省了每次旋轉的花費。

換句話說,替罪羊樹允許一定程度上的不平衡。但當不平衡的程度過大時,我們就需要重構一部分子樹了。

通常來說,這個比例值取在0.5-0.9之間。不同取值的實際效果主要吃資料,一般取0.75就無大礙了。

那麼介紹了子樹比例重構,與之密切相關的就是插入操作了。

替罪羊的插入需要寫成迭代的形式,因為要在插入之後判斷是否需要重構

1

void insert(intx)2

8int nd =root;

9for

(;;)

1019}20

int rec = 0;21

for (int i=cnt; i; i =a[i].fa) //需要找到乙個最高的不滿足條件的點進行重構

22if (std::max(a[a[i][0]].tot, a[a[i][1]].tot)*5 >= a[i].tot*4

)23 rec =i;

24if

(rec) rebuild(rec);

25 }

替罪羊的刪除似乎有兩種?

一種就是「替罪羊」做法;另一種是乾脆把要刪除的點打個標記,統計時候跳過就好了。

以下介紹「替罪羊」做法:

1

void erase(intk)2

10int s = a[k][0]?a[k][0]:a[k][1

];      //s=0表示左兒子;s=1表示右兒子

11int f = a[k].fa, d = (a[f][1]==k);

12 a[f][d] = s, a[s].fa =f;

13for(int i=f; i; i=a[i].fa)

14 a[i].tot--;

15if(root==k) root =s;

16 }

這裡寫了個迭代。

插入 x 數

刪除 x 數(若有多個相同的數,因只刪除乙個)

查詢 x 數的排名(排名定義為比當前數小的數的個數 +1 。若有多個相同的數,因輸出最小的排名)

查詢排名為 x 的數

求 x 的前驅(前驅定義為小於 x ,且最大的數)

求 x 的後繼(後繼定義為大於 x ,且最小的數)

輸入格式:

第一行為 n,表示操作的個數,下面 n 行每行有兩個數 opt 和 x , opt 表示操作的序號(1≤opt≤6 )

輸出格式:

對於操作 3,4,5,6 每行輸出乙個數,表示對應答案

那麼就直接上**吧。

1 #include2

const

int maxn = 100003;3

4int

n,cnt,root,sv[maxn],cntsv,sum,lim;

5struct

node613

}a[maxn];

1415 template void printe(const

t x)

1620 template inline void print(const

t x)

2125

char

tz()

2630 inline int

read()

3142 inline void change(int

x)43

46void hit(int

x)47

53int build(int l, int

r)54

64 inline void rebuild(int

x)65

72 inline void insert(int

x)73

79int nd =root;

80for

(;;)

8190}91

int rec = 0;92

for (int i=cnt; i; i =a[i].fa)

93if (std::max(a[a[i][0]].tot, a[a[i][1]].tot)*5 >= a[i].tot*4

)94 rec =i;

95if

(rec) rebuild(rec);96}

97int getpl(int

x)98

105return

tmp;

106}

107void erase(int

k)108

116int s = a[k][0]?a[k][0]:a[k][1

];117

int f = a[k].fa, d = (a[f][1]==k);

118 a[f][d] = s, a[s].fa =f;

119for(int i=f; i; i=a[i].fa)

120 a[i].tot--;

121if(root==k) root =s;

122}

123 inline int find(int

x)124

134}

135return

ret;

136}

137 inline int ask(int

x)138

149}

150 inline int pre(int

x)151

158 inline int suf(int

x)159

166int

main()

167179

if (tt==3) print(find(x)),putchar('\n'

);180

if (tt==4) print(ask(x)),putchar('\n'

);181

if (tt==5) print(pre(x)),putchar('\n'

);182

if (tt==6) print(suf(x)),putchar('\n'

);183

}184

return0;

185 }

替罪羊的其他例題同

「treap」那一篇的例題介紹。其實這些題用任何一種平衡樹都可以,只不過不同平衡樹維護平衡的操作不一罷了。

好像替罪羊樹的效率非常之高?

end

平衡樹 替罪羊樹

yangkai 身為平衡樹卻不做任何形式的旋轉,替罪羊樹可以稱得上是最暴力的平衡樹了。替罪羊樹 sgt 保留有二叉搜尋樹的基本性質,即對於任意乙個節點t,左兒子的所有節點比它小,右兒子的所有節點比它大。但是既然不基於翻轉,它怎樣維護平衡樹的優秀複雜度呢?sdt基於乙個叫做 重構 的操作,聽起來很是優...

替罪羊樹模版 普通平衡樹

替罪羊樹,賊長,我哭了。include include include include include using namespace std const int maxn 1e5 5 const double alpha 0.75 struct nodetzy maxn intcnt,root 新...

平衡樹學習筆記(2) 替罪羊樹

1.重構 2.插入 3.查詢 4.刪除 5.判斷重構 6.綜合運用 一些廢話 總結 中考加油!想學替罪羊樹很久了,剛開始接觸平衡樹的時候就久仰替罪羊樹的大名,但是無奈經驗和理解能力都有些欠缺,暫時放了下,這幾天題目難度不大,有了時間來學替罪羊樹。其實替罪羊樹之所以看起來高深,有80 的原因是因為名字...