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

2021-09-25 01:49:43 字數 3561 閱讀 8870

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 我也沒意見 替罪羊樹的主要思想...