1.重構
2.插入
3.查詢
4.刪除
5.判斷重構
6.綜合運用
一些廢話
總結==**(中考加油!)**==
想學替罪羊樹很久了,剛開始接觸平衡樹的時候就久仰替罪羊樹的大名,但是無奈經驗和理解能力都有些欠缺,暫時放了下,這幾天題目難度不大,有了時間來學替罪羊樹。
其實替罪羊樹之所以看起來高深,有80%的原因是因為名字的問題,其實我也不知道為什麼叫這個名字(好像是說因為乙個點導致整棵子樹重構)。但是事實上就是把一棵子樹拍扁,再重新拎起來來保證平衡(真的好暴力)。
(其實我真的沒感覺這個暴力的平衡樹有多簡單,個人感覺比splay還麻煩)
1.陣列介紹
先講講陣列的各個數的用途:
struct node
a[n*2];
key:當前節點的值。
size:當前子樹的大小(包括刪除的點)。
bz:當前節點是否被刪除(刪除為0,否則為1)。
l:該節點左子樹根節點的編號(左兒子)。
r:該節點右子樹根節點的編號(右兒子)。
g:當前子樹內還有多少個點沒有被刪掉。
2.記憶體池
我們會刪去很多節點,所以那些沒有用的節點的編號會佔據很多空間,於是我們可以建乙個記憶體池(棧)來儲存無用的節點編號,方便呼叫。
int tot=0;
int getnew()
先把這個平衡樹最特殊的東西講了…
對於一棵以x為根的子樹,我們找到它的中序遍歷(左根右),要使它重構之後的中序遍歷與原來相等,但是層數減少這樣就可以盡量滿足樹的平衡…
像這樣:
我們發現現在這棵樹真的好不平衡…
怎麼辦呢?
我們需要將它先轉成乙個陣列(按照中序遍歷):
1 5 2 3 4
然後就可以愉快地開始重構了:
先將最中間的乙個數提出來,作為整棵子樹的根(提出2)
接著對於左邊的數放到左子樹中,右邊的數放到右子樹中(即1 5放入左子樹,2 4放入右子樹)
再遞迴下去即可。
重構完以後就變成了這樣:
我們就可以得到一棵盡可能平衡的樹。
**如下:
int get_tmp(int x)//得到中序遍歷
int set(int l,int r,int &x)
(l這種做法是直接插入節點:
int insert(int &x,int z)
a[x].size++,a[x].g++,(z<=a[x].key)?insert(a[x].l,z):insert(a[x].r,z);//需要注意size和g都要加1
}
1.查詢排名第x的數的值
直接在樹上跳就好。
遇到一樣的就退出,比當前數大就往右走,否則往左走。
注意乙個點是否被刪除。
int get_z(int x)
}
2.查詢值為x的數的排名
我們可以從根節點出發,向下搜尋,每次如往右子樹走,則將答案加上左子樹以及本身的值。
記住一開始ans要為1(沒有排名為0的點)。
int get_rk(int x)
return ans;
}
這個可以說是替罪羊樹如此暴力的源泉了。
因為在替罪羊樹中,我們對於刪除的點只打上標記(由於刪除時的懶惰,我們在後面需要重構),操作時直接跳過。
**也很懶惰(言簡意賅才怪):
int del_rk(int &x,int rk)
(a[a[x].l].g+a[x].bz>=rk)?del_rk(a[x].l,rk):del_rk(a[x].r,rk-a[a[x].l].g-a[x].bz);
}int del_z(int v)
這一塊決定了替罪羊樹的時間的多邊性。
我們設alpha(在程式中是al)表示:
我們需要一棵子樹內的有用節點(即標記為1的點至少佔多少)。
這棵樹的左子樹或者右子樹最多佔整棵子樹的多少。
當然,我們可以將這兩個數分開(但是我的程式裡是將其合在了一起)。
我們可以看到,在上面del_z的函式內我們就用了al的第乙個作用。
那麼在我們插入數之後(即呼叫insert函式之後),我們就需要用到alpha來判斷這棵替罪羊樹是否需要重新平衡節點。
我們設pd函式表示根為x的節點,在插入了值為y的數之後是否需要重構。
那麼我們就從x節點開始順次下去,直到y節點。
注意我們要從上到下,遇到需要重構的子樹重構完之後就直接退出,因為這樣我們已經構出了一棵相對平衡的樹,不需要繼續下去重構。
另外,那個bc函式表示判斷該子樹時候平衡。
int bc(int x)
int pd(int x,int y)
x=f,f=(y>a[x].key)?a[x].r:a[x].l;
}}
就那麼多了嗎?
並不,以上只是很基本的幾個操作,具體的話應該大部分功能都可以靠上述函式交替呼叫實現。
1.插入
跟上面講的一樣,插入完之後直接判斷是否需要重構。
int yroot=root;
insert(root,x);
pd(yroot,x);
2.刪除
沒什麼好說的,直接呼叫del_z函式就好了。
3.查詢
直接輸出就好了。
4.查詢前驅
我們先要找到x的排名,再查詢排名為x的排名-1的數的值。
int u=get_rk(x)-1;
printf("%d\n",get_z(u));
5.查詢後繼
道理差不多,自行理解。
int u=get_rk(x+1);
printf("%d\n",get_z(u));
其實我個人覺得替罪羊樹不是特別實用(大佬勿噴)
它跟splay比的優點就是:
比較易懂,不用轉來轉去,不會燒腦。
常數小(吧)…
可以手動調al的值,讓它可以控制重構的次數,以在某些資料有特殊情況的題中暴力出奇蹟。
另外,下面講一講關於al的值的調整方法(al的值應在(0.5,1)):
首先,我們要明白,當重構的次數少的時候,樹內無用的節點就會多,樹也會不平衡,但是如果重構次數過多,我們重構需要的時間也就無法保證,可能會退化成暴力。
在詢問較多,我們可以將al設小一點,這樣重構次數會多,查詢用的時間也就少了。
在修改較多時,al要調大一點,以保證重構次數較少。
其實平衡樹這種東西,還是要多做題目多練手,這樣才不會生疏。
平衡樹 替罪羊樹
yangkai 身為平衡樹卻不做任何形式的旋轉,替罪羊樹可以稱得上是最暴力的平衡樹了。替罪羊樹 sgt 保留有二叉搜尋樹的基本性質,即對於任意乙個節點t,左兒子的所有節點比它小,右兒子的所有節點比它大。但是既然不基於翻轉,它怎樣維護平衡樹的優秀複雜度呢?sdt基於乙個叫做 重構 的操作,聽起來很是優...
替罪羊樹學習筆記
教練講旋轉的時候摸魚去了,然後就不會旋轉操作了t t,那怎麼辦呢,要做題的啊,誒,替罪羊樹好像是不用旋轉的誒qwq,就它了。替罪羊樹這樣直接講不直觀,還是看題來講吧。上題 洛谷 p3369 模板 普通平衡樹 概念 思想 替罪羊樹屬於平衡樹的一種,但是他維護平衡的方式不是複雜的旋轉,而是直接把這棵子樹...
替罪羊樹學習筆記
部落格咕咕咕了好久 最近會逐步繼續恢復更新部落格的。最近又在學習二叉搜尋樹。實測發現替罪羊樹快的飛起 時間約splay的1 2 寫起來還比較簡單,決定來一波。那為什麼還要用splay呢?因為splay是序列之王!還能維護lct!你要用非旋treap fhq treap 我也沒意見 替罪羊樹的主要思想...