替罪羊樹
前言
暴力即優雅
替罪羊樹是一種依賴於暴力重構操作維持平衡的平衡樹
它利用了一個平衡因子\(α\)來維持平衡
\(α\)通常設0.7
當一棵子樹不滿足平衡因子的條件的時候
我們就把這棵子樹拍扁重建
聽起來就很暴力
在別人嗷嗷亂轉的時候直接一巴掌上去
一打一個不吱聲
感謝xixi的程式碼
平衡樹(FHQ/Splay/替罪羊)
操作
重構
替罪羊樹之所以能夠平衡,是在於其重構時不是瞎重構,而是將被重構的子樹重構為一棵完全二叉樹
需要一個輔助的陣列記錄中序遍歷的序列
int rebuild(int l,int r)
{
if(l==r)
{
tr[t[l]]={tr[t[l]].v,1,1,1,0,0};
return t[l];
}
int mid=l+r>>1;
while(mid<r && tr[t[mid]].v==tr[t[mid+1]].v) mid++;
int x=t[mid];
ls(x)=l<mid?rebuild(l,mid-1):0;
rs(x)=r>mid?rebuild(mid+1,r):0;
tr[x].siz=tr[ls(x)].siz+tr[rs(x)].siz+1;
tr[x].fac=tr[ls(x)].fac+tr[rs(x)].fac+1;
return x;
}
檢查平衡
檢查左右子樹之一的大小佔其本身子樹大小的比例是否超過 \(α\)
如果不平衡就拍扁重建
就像這種 是不是看起來就欠拍不平衡
void check(int &x,int ed)
{
if(x==ed) return;
if(max(tr[ls(x)].siz,tr[rs(x)].siz)>tr[x].siz*0.7 || tr[x].siz-tr[x].fac>tr[x].siz*0.3)
{
t.clear();
dfs(x);
x=t.empty()?0:rebuild(0,t.size()-1);
return;
}
check(tr[x].s[tr[ed].v>tr[x].v],ed);
tr[x].siz=tr[ls(x)].siz+tr[rs(x)].siz+1;
}
插入
替罪羊樹的插入操作與其他的二叉搜尋樹差不多。
只不過因為其導致了樹形態的改變
我們在插入完回溯的過程中還需要判斷一下是否需要重構
void Insert(int &x,int v)
{
if(!x)
{
tr[x=++cnt]={v,1,1,1};
check(rt,x);
return;
}
tr[x].siz++,tr[x].fac++;
Insert(tr[x].s[v>tr[x].v],v);
}
刪除
替罪羊樹使用惰性刪除(就是打標記刪),只需要將對應節點上代表節點內資料數量的標記自減一即可。
void delet(int x,int v)
{
tr[x].fac--;
if(tr[x].fg && tr[x].v==v)
{
tr[x].fg=0;
check(rt,x);
}
else delet(tr[x].s[v>tr[x].v],v);
}
查詢
跟BST相同
判斷該找左子樹還是右子樹
遞迴找即可
int getrank(int v)
{
int x=rt,k=1;
while(x)
{
if(v<=tr[x].v) x=ls(x);
else k+=tr[x].fg+tr[ls(x)].fac,x=rs(x);
}
return k;
}
int getval(int k)
{
int x=rt;
while(x)
{
if(tr[x].fg && tr[ls(x)].fac+tr[x].fg==k) break;
if(tr[ls(x)].fac>=k) x=ls(x);
else k-=tr[ls(x)].fac+tr[x].fg,x=rs(x);
}
return tr[x].v;
}
有了之前的基礎
感覺平衡樹越來越好學
再次感謝xixi的程式碼
(號稱全網最佳)
完結撒花
引用來源
替罪羊樹~講解
替罪羊樹(Scapegoat Tree)-CSDN部落格
替罪羊樹 - OI Wiki (oi-wiki.org)