数据结构 平衡树treap

最近闲来无事,就研究了下平衡树,写了个treap,最后搞出了数组模拟的版本,(宝宝真的不喜欢指针),大概就是这样子,写篇博客记录一下。

我们考虑一颗搜索二叉树,如果这颗二叉树长得很想一棵树,那么我们显然可以在Ologn的时间内查询元素,然而我们很多时候搜索二叉树可能会被卡成一条链,这样子复杂度就变成了On的了,显然是不科学的,所以我们要让这颗搜索二叉树尽可能的平衡。平衡有很多方式,这里我们讲一下treap。


treap顾名思义,就是树堆,我们想,如果随机出来很多数字,把他们组成一个堆,那么在理想情况下,这个堆应该是一个非常平衡的树的。所以我们在这里对于每一个点,我们不光维护他的data,还维护他随机出来的一个优先级。之后我们在保证搜索二叉树的情况下,保证堆的性质,就可以了。关键是如何保证堆的性质,显然我们需要对一些元素进行位置移动,所以我们要在这讲一下左旋和右旋。


图示是将pivot点左旋产生的效果,我们可以发现,在进行旋转后,并没有改变搜索二叉树的性质,并且还做到了元素的移动。

而右旋则是从右边变成左面。可以认为,左旋是让一个元素下降,右旋是让一个元素上升,我们可以借助他们做到维护堆的性质的同时,不改变二叉树的性质。

实现则十分简单,交换几个指针即可。(下文使用数组模拟指针)


然后我们来考虑一下,如何查询,这非常简单,我们只要按照搜索二叉树的性质即可,如果要搜索的值小于当前节点的值,就到左子树去找,否则到右侧。


我们来考虑如何删除一个元素,我们想一个节点无非有3种情况,要么没有子节点,直接删除指向他的指针即可。要么有一个子节点,这时候你直接把他删掉,上下接上即可。我们来考虑最复杂的第三种情况,我们考虑要删除的节点的左子节点的右子节点的……的右子节点,一定是,比要删除的点小的点中最大的。所以用这个点来替换要删除的点是毫无问题的。这里注意只替换值,不替换用于维护堆的性质的优先级。


大概就讲到这里,代码里有许多简化代码长度的小技巧,大家可以学习一下。

int s[siz][2], v[siz], pri[siz], tot, root;
int cmp(int a, int b)
{
	if(v[a] == b) return -1;
	return v[a] < b;
}
void rotate(int &x, int k)
{
	int o = s[x][k];
	s[x][k] = s[o][k^1];
	s[o][k^1] = x;
	x = o;
}
void insert(int &x, int k)
{
	if(!x) 
	{
		v[x = ++tot] = k;
		pri[x] = rand();
		return;
	}
	int t = cmp(x, k);
	if(t != -1)
	{
		insert(s[x][t], k);
		if(pri[s[x][t]] > pri[x]) rotate(x, t);
	}
}
bool ask(int x, int k)
{
	if(!x) return false;
	int t = cmp(x, k);
	if(t == -1) return true;
	return ask(s[x][t], k);
}
void rem(int &);
void frem(int &x, int &k)
{
	int cur = s[x][0];
	if(!s[cur][1])
	{
		swap(v[cur], k);
		rem(s[x][0]);
		return;
	}
	while(s[s[cur][1]][1]) cur = s[cur][1];
	swap(v[x],v[s[cur][1]]);
	rem(s[cur][1]);
}
void rem(int &x)
{
	if(!s[x][0] && !s[x][1])
	{
		x = 0;
		return;
	}
	if(!s[x][0] ^ !s[x][1])
	{
		int t = s[x][1] > 0;
		x = s[x][t];
		return;
	}
	frem(x, v[x]);
}
void remove(int &x, int k)
{
	if(!x) return;
	int t = cmp(x, k);
	if(t == -1)
	{
		rem(x);
		return;
	}
	remove(s[x][t], k);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值