平衡树之Treap

一个集合支持快速插入、删除一个数字。
支持快速查找一个数字在所有已插入数字中的排名。
支持删除大小在某一个区间内的数字。

动态维护一个数列。可以在数列的任何位置插入删除,求区间和,MinMax,进行区间翻转


这就需要用到二叉查找树( Binary SearchTree)。
性质:
这是一棵二叉树。
对于任意一个节点,左子树的所有节点权值小于该节点权值,右子树所有节点权值大于该节点权值。

常见二叉查找树-------Treap

Treap在基本的二叉搜索树基础上增加了一个『随机附加域』,整棵树不仅要满足二叉搜索左儿子小右儿子大的性质,同时也要满足按照『随机附加域』成一个。正因为附加域的随机性,使得Treap可以保持一个较为随机的结构,其平均树高为O(logn)级别,这也是它能够实现基本操作时间复杂度O(logn)的原因。 

Treap的存储
通常来说我们用一个结构体来存储一个Treap节点:
struct treap_node
{
int value,size,key;
//value为点权值
//key为随机附加域
//size为子树的大小
treap_node *ch[2];
//记录左右儿子的指针
}
因为平衡树是一个动态的结构,所以节点需要动态的开(内存池

int  tot=0;//记录当前内存池中哪些节点已经使用
 获取一个新节点:
 inlinetreap_node *get_new(int value){
treap_node*now = pool + ++top;
now->value =value;
now->size =1;
now->key = rand();
now->ch[0] = now->ch[1] = null
returnnow;
}
上面的代码里面出现了一个null,注意和空指针NULL区分。
因为访问空指针会引起RE,为了避免麻烦,我们自己造一个假的空指针,这个指针null指向一个没有用的结构体。
nullsize=0key=INF,这样根据堆的性质能保证它待在最底下而不会跑上来

Treap的旋转

wh=0;(左孩子)

treap_node *child = now->ch[wh];

now->ch[wh] = child->ch[wh ^ 1];

child->ch[wh ^ 1] = now;

now->update();

child->update();


我们的Treap节点会维护一些和子树有关的信息,比如size(子树里面节点的个数)。当子树发生变化的时候,就需要更新这些信息:

void treap_node::update()

{

size=1+ch[0]->size+ch[1]->size;(本体+左孩子+右孩子)

}


在实际应用中,update函数还有可能维护一些别的信息,比如子树里面节点权值的最大者,节点权值和等等。

插入新节点的步骤:
先获取一个新节点
找到该新节点应该插入的位置。
根据随机附加域进行调整,直到满足堆的性质(旋转)。

void insert(treap_node *&now, int value)
{
if (now==null)
 {
  now=get_new(value);
  return;
 }
if (now->value==value) return;
int wh=value < now->value ? 0 : 1;
insert(now->ch[wh], value);
now->update();
int minwh=  
  now->ch[0]->key < now->ch[1]->key ? 0:1;
if (now->ch[minwh]->key  <  now->key)
  rotate(now,minwh);//旋转
}
Treap的删除
如果一个节点是叶子节点,我们能够很方便的删除。
所以我们的思路是,把要删除的点挪到叶子节点的位置。
•一路旋转,注意还要维护堆的性质。
void del(treap_node *&now, int value)
{
if (now==null) return;
if (now->value == value)
{
int minwh=
   now->ch[0]->key < now->ch[1]->key ?0:1;
if (now->ch[minwh]!=null)
{
rotate(now,minwh);
del(now->ch[minwh^1],value);
now->update();
}
else now=null;
}
else
{
int wh=value < now->value ? 0 :1;
del(now->ch[wh],value);
now->update();
}
}
Treap的查找
insert,del函数中的查找方式一样。值小向左走,值大向右走。

Treap计算Rank
计算一个数字当前是第几小(大)的
首先不停的查找这个数字。如果当前要向左走,答案不变;如果当前要向右走,答案要+左子树的大小+1
int rank(treap_node *now,int value)
{
if (now==null) return 0;
int left_size = now->ch[0]->size;
if (now->value == value)
 return left_size;
else if (value < now->value)
 return rank(now->ch[0],value);
else
 return left_size + 1 + rank(now->ch[1],value);
}
Treap找第k
这个过程和计算Rank正好相反。
看一下k和当前左子树大小的关系:
如果k<=ch[0]->size,往左走。
如果ch[0]->size+1==kbingo
如果ch[0]->size+1<k,往右子树走,k-=ch[0]->size+1
int zd(treap_node *now,int k)
{
if (now==null) return INF;
int la=now->ch[0]->size + 1;
if (la==k)
 return now->value;
else if (la<k)
 return zd(now->ch[0],k);
else
 return zd(now->ch[1],k-la);
 } 
Treap相对于其它平衡树的优点是实现方便,且常数比较优良。
缺点是功能不够强大。所以我们竞赛中通常采用splay而非treap

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
using namespace std;

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

struct treap_node 
{
	int value, size, key;
	treap_node *ch[2];
	void update() 
	{
		size = ch[0]->size + ch[1]->size + 1;
	}
} pool[MAX_Q], *root, *null;
int top = 0;

inline treap_node *get_new(int value)
{
	treap_node *now = pool + ++top;
	now->value = value;
	now->size = 1;
	now->key = rand() % INF;
	now->ch[0] = now->ch[1] = null;
	return now;
}

inline void rotate(treap_node *&now, int wh)
{
	treap_node *child = now->ch[wh];
	now->ch[wh] = child->ch[wh ^ 1];
	child->ch[wh ^ 1] = now;
	now->update();
	child->update();
	now = child;
}

void insert(treap_node *&now, int value) {
	if (now == null) {
		now = get_new(value);
		return;
	}
	if (now->value == value)
		return;
	int wh = value < now->value ? 0 : 1;
	insert(now->ch[wh], value);
	now->update();
	int minwh = 
		now->ch[0]->key < now->ch[1]->key ? 0 : 1;
	if (now->ch[minwh]->key < now->key)
		rotate(now, minwh);
}

void del(treap_node *&now, int value) {
	if (now == null) return;
	if (now->value == value) {
		int minwh = 
			now->ch[0]->key < now->ch[1]->key ? 0 : 1;
		if (now->ch[minwh] != null) {
			rotate(now, minwh);
			del(now->ch[minwh ^ 1], value);
			now->update();
		}
		else
			now = null;
	}
	else {
		int wh = value < now->value ? 0 : 1;
		del(now->ch[wh], value);
		now->update();
	}
}

inline int kth(treap_node *now, int k)
{
	if (now == null) return INF;
	int left_size = now->ch[0]->size;
	if (k <= left_size)
		return kth(now->ch[0], k);
	else if (k == left_size + 1)
		return now->value;
	else
		return kth(now->ch[1], k - left_size - 1);
}

int cou(treap_node *now, int value)
{
	if (now == null) return 0;
	int left_size = now->ch[0]->size;
	if (now->value == value) 
		return left_size;
	else if (value < now->value)
		return cou(now->ch[0], value);
	else
		return left_size + 1 + cou(now->ch[1], value);
}

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()
{
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
	srand(233);
	
	null = pool;
	null->value = 0;
	null->size = 0;
	null->key = INF;
	null->ch[0] = null->ch[1] = null;
	root = null;

	int q = get_num();
	while (q--)
	{
		char order;
		while (!isalpha(order = getchar()));
		int _ = get_num();
		switch(order)
		{
			case 'I':
				insert(root, _);
				break;
			case 'D':
				del(root, _);
				break;
			case 'K':
				int ans;
				ans = kth(root, _);
				if (ans == INF)
					printf("invalid\n");
				else
					printf("%d\n", ans);
				break;
			case 'C':
				printf("%d\n", cou(root, _));
				break;
		}
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值