平衡树之splay

--在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方。伸展树应运而生。伸展树是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。 

大家只需要记住,每次进行插入/查询的时候,都要把插入/查询的元素通过旋转变到根的位置,splay的单次操作均摊复杂度就是O(logn)的。

Splay的存储
与Treap不同的地方,Splay里面不需要key(随机附加域)这个东西,而需要额外记录该节点的父亲。
struct splay_node{
–int value,size;
–splay_node *pre, *ch[2];
•}

同样,我们也建议通过开内存池的方式实现动态分配节点,以及自己声明一个假空指针null代替系统的NULL

Splay的旋转

update函数
Treap一样
set_ch函数// A->set_ch(wh,now);   将now接在A的wh上
为了方便更改儿子
void splay_node::set_ch(intwh,splay_node *child){ 
•ch[wh] = child; 
•if (child != null) child->pre = this; 
•update();
}

inline void rotate(splay_node *&now)
{
splay_node *old_father = now->pre,
          *grand = now->pre->pre;
int wh=now->get_wh();//判断是不是儿子
old_father->set_ch(wh, now->ch[wh^1]);
now->set_ch(wh^1,old_father);
now->pre = grand;
if (grand!=null)
 grand->ch[grand->ch[0]==old_father ? 0:1] = now; 
}


Splay的伸展

所谓伸展,就是将某个点不停的向上旋转,直到旋转到某一个规定的位置(通常是根)。
•如果当前点,父亲,爷爷呈一条直线,我们先转父亲再转自己

如果当前点,父亲,爷爷扭曲,我们连续转两次自己。

inline void splay(splay_node *now, splay_node *tar)
{
for (;now->pre != tar ; rotate(now))
 if (now->pre->pre!=tar)
   now->get_wh()==now->pre->get_wh() ?
       rotate(now->pre) : rotate(now);
if (tar == null) root=now;
}

其中now是我们要伸展的点,tar是我们的目标:当我们伸展到now的父亲为tar的时候,就不再伸展了。如果我们想让now伸展到根,就把tar设置为null

Splay的插入

Treap类似。只不过插入了以后,不再需要维护堆的性质进行调整,而是直接把新节点伸展到根。
void insert(int value)
{
splay_node *last = null, *now = root;
splay_node *newone = get_new(value);

while (now != null)
{
last = now;
if (newone->value == now->value)
{
now->cnt++; now->size++;
splay(now, null);
return;
}
if (newone->value < now->value)
now = now->ch[0];
else
now = now->ch[1];
}
if (last == null)
root = newone;
else
{
if (newone->value < last->value)
last->set_ch(0, newone);
else
last->set_ch(1, newone);
splay(newone, null);
}
}


Splay的删除

Splay的删除方式和Treap不太相同。
首先找到要删除的节点,然后把它伸展(splay)到根的位置。再分三种情况:
1.
无左右儿子,直接删除节点。
2. 只有一个儿子,令独生子变成根,删去该点。
3. 左右儿子都有。找到左子树中权值最大的点,将其splay到左儿子的位置。然后将整棵右子树挂在左儿子的右边。删除节点。 
inline splay_node *find(int value)//查找
{
splay_node *now = root;
while (now != null)
{
if (now->value == value)
break;
if (value < now->value)
now = now->ch[0];
else
now = now->ch[1];
}
if (now != null) splay(now, null);
return now;
}

void del(int value)
{
splay_node *now = find(value);//查找
if (now == null) return;
if (now->cnt > 1)//有很多这个数啊啦啦啦
{
now->cnt--;
now->size--;
return;
}

if (now->ch[0] == null && now->ch[1] == null)//第一种
root = null;
else if (now->ch[1] == null)//第二种
{
now->ch[0]->pre = null;
root = now->ch[0];
}
else if (now->ch[0] == null)
{
now->ch[1]->pre = null;
root = now->ch[1];
}
else//第三种
{
splay_node *_ = now->ch[0];
while (_->ch[1] != null) _ = _->ch[1];
splay(_, now);
_->set_ch(1, now->ch[1]);
_->pre = null;
root = _;
}
}

Splay的区间操作

Splay相比Treap的优势是能够更好的实现区间操作。
在进行区间操作之前我们通常要把我们要进行操作的区间弄到一棵子树上。
我们要对区间[L,R]进行操作,我们首先获得L-1节点和R+1节点。我们把L-1节点splay到根,再把R+1节点splay到根的右儿子位置。那么现在根的右儿子的左儿子这整棵子树就是我们要的区间。
因为我们在splay的过程中,一直会进行update,所以每个节点维护的附加信息(比如size,我们还可以加入summinmax等等)都一直是正确的。访问根节点右儿子的左儿子的信息,就可以直接获得区间信息。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
using namespace std;

const int INF = 1e9;
const int MAX_Q = 1e5 + 10;

struct splay_node 
{
	int value, size, cnt;
	splay_node *pre, *ch[2];
	void update() 
	{
		size = ch[0]->size + ch[1]->size + cnt;
	}
	int get_wh()
	{
		return pre->ch[0] == this ? 0 : 1;
	}
	void set_ch(int wh, splay_node *child);
} pool[MAX_Q], *root, *null;

void splay_node::set_ch(int wh, splay_node *child)
{
	ch[wh] = child;
	if (child != null) child->pre = this;
	update();
}

int top = 0;

inline splay_node *get_new(int value)
{
	splay_node *now = pool + ++top;
	now->value = value;
	now->size = 1;
	now->cnt = 1;
	now->pre = now->ch[0] = now->ch[1] = null;
	return now;
}

inline void rotate(splay_node *&now)
{
	splay_node *old_father = now->pre, 
		*grand = now->pre->pre;
	int wh = now->get_wh();
	old_father->set_ch(wh, now->ch[wh ^ 1]);
	now->set_ch(wh ^ 1, old_father);
	now->pre = grand;
	if (grand != null)
		grand->ch[grand->ch[0] == old_father ? 0 : 1] = now;
}

inline void splay(splay_node *now, splay_node *tar)
{
	for (; now->pre != tar; rotate(now))
		if (now->pre->pre != tar)
			now->get_wh() == now->pre->get_wh() ? 
				rotate(now->pre) : rotate(now);
	if (tar == null) root = now;
}

void insert(int value)
{
	splay_node *last = null, *now = root;
	splay_node *newone = get_new(value);
	
	while (now != null)
	{
		last = now;
		if (newone->value == now->value)
		{
			now->cnt++; now->size++;
			splay(now, null);
			return;
		}
		if (newone->value < now->value)
			now = now->ch[0];
		else
			now = now->ch[1];
	}

	if (last == null)
		root = newone;
	else
	{
		if (newone->value < last->value)
			last->set_ch(0, newone);
		else
			last->set_ch(1, newone);
		splay(newone, null);
	}
}

inline splay_node *find(int value)
{
	splay_node *now = root;
	while (now != null)
	{
		if (now->value == value)
			break;
		if (value < now->value)
			now = now->ch[0];
		else
			now = now->ch[1];
	}
	if (now != null) splay(now, null);
	return now;
}

inline int pre(int value)
{
	int ans = -INF;
	splay_node *now = root;
	while (now != null)
	{
		if (now->value < value)
		{
			ans = max(ans, now->value);
			now = now->ch[1];
		} else
			now = now->ch[0];
	}
	return ans;
}

inline int nxt(int value)
{
	int ans = INF;
	splay_node *now = root;
	while (now != null)
	{
		if (now->value > value)
		{
			ans = min(ans, now->value);
			now = now->ch[0];
		} else
			now = now->ch[1];
	}
	return ans;
}

void del(int value)
{
	splay_node *now = find(value);
	if (now == null) return;
	if (now->cnt > 1)
	{
		now->cnt--;
		now->size--;
		return;
	}

	if (now->ch[0] == null && now->ch[1] == null)
		root = null;
	else if (now->ch[1] == null)
	{
		now->ch[0]->pre = null;
		root = now->ch[0];
	}
	else if (now->ch[0] == null)
	{
		now->ch[1]->pre = null;
		root = now->ch[1];
	}
	else
	{
		splay_node *_ = now->ch[0];
		while (_->ch[1] != null) _ = _->ch[1];
		splay(_, now);
		_->set_ch(1, now->ch[1]);
		_->pre = null;
		root = _;
	}
}

inline int get_rank(int value)
{
	splay_node *now = root;
	int left_size = 0;
	while (now != null)
	{
		if (value == now->value)
		{
			int ans = left_size + now->ch[0]->size + 1;
			splay(now, null);
			return ans;
		}
		if (value < now->value)
			now = now->ch[0];
		else
			left_size += now->ch[0]->size + now->cnt, now = now->ch[1];
	}
	return -1;
}

inline int kth(int k)
{
	splay_node *now = root;
	int left_size = 0;
	while (now != null)
	{
		int _ = left_size + now->ch[0]->size;
		if (_ + 1 <= k && k <= _ + now->cnt)
		{
			splay(now, null);
			return now->value;
		}
		if (k <= _) now = now->ch[0];
		else left_size = _ + now->cnt, now = now->ch[1];
	}
	return -1;
}

inline int get_num()
{
	int num = 0;
	char c;
	bool flag = false;
	while ((c = getchar()) == ' ' || c == '\n' || c == '\r');
	if (c == '-') flag = true;
	else num = c - '0';
	while (isdigit(c = getchar()))
		num = num * 10 + c - '0';
	return (flag ? -1 : 1) * num;
}

int main()
{
	null = pool;
	null->value = 0;
	null->size = 0;
	null->cnt = 0;
	null->pre = null->ch[0] = null->ch[1] = null;
	root = null;

	int q = get_num();
	while (q--)
	{
		int order = get_num();
		int _ = get_num();
		switch(order)
		{
			case 1:
				insert(_);
				break;
			case 2:
				del(_);
				break;
			case 3:
				printf("%d\n", get_rank(_));
				break;
			case 4:
				printf("%d\n", kth(_));
				break;
			case 5:
				printf("%d\n", pre(_));
				break;
			case 6:
				printf("%d\n", nxt(_));
				break;
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值