「学习笔记」替罪羊树

Scapegoat Tree

前言

以前觉得树形结构都难的要死,虽然现在也没好多少,但是明显对树形结构的了解要远超从前了。
平衡树什么的,以前总觉得遥不可及。什么 Treap 什么 Splay ,一听就很高端大气上档次啊,想必是及其优美的高级算法。
替罪羊树是一种较为简单的自平衡二叉搜索树,名字听起来还十分霸气,我想以替罪羊树作为跳板,揭开平衡树神秘的面纱。
以上200%为瞎扯)这也是瞎扯)


特点

一般的平衡树都依赖于旋转操作,而替罪羊树不一样(因为是重量平衡树),它选择及其优雅的方式维护性质。至于你问怎么个优雅法?暴力即是优雅!
仔细想来,替罪羊树似乎与BST有几分相像,因为是平衡树, 做为一棵平衡树,它会在子树失去平衡后暴力重构以保证子树满足平衡树的性质(定义一个平衡树因子 α \alpha α ,对替罪羊树的每个节点 t t t,都需要满足: max ⁡ ( s i z e l , s i z e r ) ⩽ α × s i z e t \max\rm(size_l,size_r) \leqslant \alpha\times size_t max(sizel,sizer)α×sizet)。


时间复杂度

可以保证,除重构外,所有的操作的平摊最坏时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn),而重构是 O ( n ) O(n) O(n),但均摊下来也是 l o g log log 级别。


  • 插入

    插入的操作特别容易,
    就是暴力搜空缺处插入。
//插入元素
inline void Insert(int &x,int val) {
	//若结点为空,将元素插入
	if (!x) {
		x=Void[tot--];
		node[x].Val=val;
		node[x].Exist=1;
		Build(x);
		
		return;
	}
	
	//子树大小+1
	++node[x].Size;
	++node[x].Fac;
	
	//将待插入元素与当前元素的大小比较,继续搜索当前结点的左子树或右子树
	if (val<=node[x].Val) {
		Insert(node[x].Son[0],val);
	}
	else {
		Insert(node[x].Son[1],val);
	}
}

  • 删除

    将结点删除(打上标记),
    该结点所在的子树大小-1。
//删除排名为rank的数
inline void Delete_rank(int &x,int rk) {
	//如果当前结点存在且排名为rank,删除结点
	if (node[x].Exist && !((node[node[x].Son[0]].Fac+1)^rk)) {
		//删除结点,结点所在子树大小-1
		node[x].Exist=0;
		--node[x].Fac;
		
		return;
	}

	//子树实际大小-1
	--node[x].Fac;
	
	//比较需删除元素与当前元素,确定搜索左子树还是右子树
	if (node[node[x].Son[0]].Fac+node[x].Exist>=rk) {
		Delete_rank(node[x].Son[0],rk);
	}
	else {
		Delete_rank(node[x].Son[1],rk-node[x].Exist-node[node[x].Son[0]].Fac);
	}
}

//删除值为v的数
inline void Delete_val(int v) {
	//将操作转化为删除排名为(v的排名)的数
	Delete_rank(rt,get_rank(v));

	//维护平衡树性质,0.5<=alpha<=1.0,一般取0.75
	if ((double)node[rt].Size*alpha>(double)node[rt].Fac) {
		ReBuild(rt);
	}
}

  • 查询

    询问值为val的数的排名 或 排名为rank的数的值
    暴力搜索。
//询问值为val的数的排名
inline int get_rank(int v) {
	int x=rt,rk=1;
	//结点不为空时进行搜索
	while (x) {
		//判断当前元素是否大于val,是就访问左子数,否则累加当前元素排名,访问右子树
		if (node[x].Val>=v) {
			x=node[x].Son[0];
		}
		else {
			rk+=node[node[x].Son[0]].Fac+node[x].Exist;
			x=node[x].Son[1];
		}
	}
	
	return rk;
}

//询问排名为rank的数的值
inline int get_val(int rk) {
	int x=rt;
	//结点不为空时进行搜索
	while (x) {
		//若当前元素排名为rank则返回,否则判断当前元素的排名是否小于rk,决定访问左子数,或访问右子树,rank减去当前元素排名
		if (node[x].Exist && node[node[x].Son[0]].Fac+1==rk) {
			return node[x].Val;
		}
		if (node[node[x].Son[0]].Fac>=rk) {
			x=node[x].Son[0];
		}
		else {
			rk-=node[x].Exist+node[node[x].Son[0]].Fac;
			x=node[x].Son[1];
		}
	}
}
  • 重构

    重构的过程就是把数压扁,然后吊起来挂着。

随便举个例子:

显然不合法,那就把它压扁,

然后吊起来挂着,

操作就完成了。

//压扁树
#include <bits/stdc++.h>
#define N 100005
#define alpha 0.75
using namespace std;

int n,st,rt,cnt,tot,cur[N],Void[N];
struct ScapegoatTree {
	int Son[2],Exist,Val,Size,Fac;
}node[N];

inline bool balance(int x) {
	return (double)node[x].Fac*alpha>(double)max(node[node[x].Son[0]].Fac,node[node[x].Son[1]].Fac);
}

inline void read(int &x) {
	char ch=getchar();
	while (ch<'0' || ch>'9') {
		ch=getchar();
	}
	for (x=0;ch>='0' && ch<='9';ch=getchar()) {
		x=(x<<1)+(x<<3)+ch-'0';
	}
}

inline void write(int x) {
	if (x<0) {
		putchar('-');
		x=abs(x);
	}
	if (x>9) {
		write(x/10);
	}
	putchar(x%10+'0');
}

inline void Init() {
	tot=0;
	for (register int i=N-1;i;--i) {
		Void[++tot]=i;
	}
}

inline void Build(int x) {
	node[x].Son[0]=node[x].Son[1]=0;
	node[x].Size=node[x].Fac=1;
}

inline void Insert(int &x,int val) {
	if (!x) {
		x=Void[tot--];
		node[x].Val=val;
		node[x].Exist=1;
		Build(x);
		
		return;
	}
	++node[x].Size;
	++node[x].Fac;
	if (val<=node[x].Val) {
		Insert(node[x].Son[0],val);
	}
	else {
		Insert(node[x].Son[1],val);
	}
}

inline void Traversal(int x) {
	if (!x) {
		return;
	}
	Traversal(node[x].Son[0]);
	if (node[x].Exist) {
		cur[++cnt]=x;
	}
	else {
		Void[++tot]=x;
	}
	Traversal(node[x].Son[1]);
}

inline void PushUp(int x) {
	node[x].Size=node[node[x].Son[0]].Size+node[node[x].Son[1]].Size+1;
	node[x].Fac=node[node[x].Son[0]].Fac+node[node[x].Son[1]].Fac+1;
}

inline void SetUp(int l,int r,int &x) {
	int mid=(l+r)>>1;
	x=cur[mid];
	if (l==r) {
		Build(x);
		
		return;
	}
	if (l<mid) {
		SetUp(l,mid-1,node[x].Son[0]);
	}
	else {
		node[x].Son[0]=0;
	}
	SetUp(mid+1,r,node[x].Son[1]);
	PushUp(x);
}

inline void ReBuild(int &x) {
	cnt=0;
	Traversal(x);
	if (cnt) {
		SetUp(1,cnt,x);
	}
	else {
		x=0;
	}
}

inline void check(int x,int val) {
	int s=val<=node[x].Val?0:1;
	while (node[x].Son[s]) {
		if (!balance(node[x].Son[s])) {
			ReBuild(node[x].Son[s]);
			return;
		}
		x=node[x].Son[s];
		s=val<=node[x].Val?0:1;
	}
}

inline int get_rank(int v) {
	int x=rt,rk=1;
	while (x) {
		if (node[x].Val>=v) {
			x=node[x].Son[0];
		}
		else {
			rk+=node[node[x].Son[0]].Fac+node[x].Exist;
			x=node[x].Son[1];
		}
	}
	
	return rk;
}

inline int get_val(int rk) {
	int x=rt;
	while (x) {
		if (node[x].Exist && node[node[x].Son[0]].Fac+1==rk) {
			return node[x].Val;
		}
		if (node[node[x].Son[0]].Fac>=rk) {
			x=node[x].Son[0];
		}
		else {
			rk-=node[x].Exist+node[node[x].Son[0]].Fac;
			x=node[x].Son[1];
		}
	}
}

inline void Delete_rank(int &x,int rk) {
	if (node[x].Exist && !((node[node[x].Son[0]].Fac+1)^rk)) {
		node[x].Exist=0;
		--node[x].Fac;
		
		return;
	}
	--node[x].Fac;
	if (node[node[x].Son[0]].Fac+node[x].Exist>=rk) {
		Delete_rank(node[x].Son[0],rk);
	}
	else {
		Delete_rank(node[x].Son[1],rk-node[x].Exist-node[node[x].Son[0]].Fac);
	}
}

inline void Delete_val(int v) {
	Delete_rank(rt,get_rank(v));
	if ((double)node[rt].Size*alpha>(double)node[rt].Fac) {
		ReBuild(rt);
	}
}

int main() {
	Init();
	read(n);
	for (;n;--n) {
		int opt,x;
		read(opt);
		read(x);
		switch (opt) {
			case 1:st=rt,Insert(rt,x),check(st,x);break;
			case 2:Delete_val(x);break;
			case 3:write(get_rank(x)),putchar('\n');break;
			case 4:write(get_val(x)),putchar('\n');break;
			case 5:write(get_val(get_rank(x)-1)),putchar('\n');break;
			case 6:write(get_val(get_rank(x+1))),putchar('\n');break;
		}
	}
	return 0;
}

后 记 后记
其实还是很容易理解的,稍微想一想,构一构图,替罪羊树就手打出来了
平衡树嘛,切多了可能就会了 吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值