【部落格】替罪羊樹

zysssss發表於2024-03-19

替罪羊樹

前言

暴力即優雅

替罪羊樹是一種依賴於暴力重構操作維持平衡的平衡樹

它利用了一個平衡因子\(α\)來維持平衡

\(α\)通常設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;
}

檢查平衡

檢查左右子樹之一的大小佔其本身子樹大小的比例是否超過 \(α\)

如果不平衡就拍扁重建

image

就像這種 是不是看起來就欠拍不平衡

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)