Treap小解析(Part 1 of 2)

16 篇文章 0 订阅
3 篇文章 0 订阅

我们先来看一道题:POJ1442

这道题题面很长,大意是给n各数字和m个询问,对于每一个m[i],要从原来n个数字的前m[i]个数字中挑出第i小的数字。

由于题目是先告诉所有数据,再不断询问第x小的数字,那么就可以先给原数组排个序。输入的询问是非递减的,所以从实现的角度来说就是不断往排序好了的数组中继续添加数字,然后查找第i小的元素。我们可以选用二叉排序树来存储数据,因为往里添数据的话时间复杂度只有O(logn)左右,但是标准的二叉排序树有可能退化成一条链,这样插入的时间复杂度就会是O(n)。所以这里我们使用一种更好的建立在排序树之上的解决方案,就是treap。

treap等于tree+heap。也就是说结构都是二叉树,在满足了左孩子小于根节点而右孩子大于根节点的基础上,在每个节点上都增加了一个变量叫做优先级,优先级是用来调整树结构用的。举个例子,比如输入的数是15 5 4,那么排序二叉树是这样建立的:

假设我们已经在每个节点上放了个优先级,再继续假设节点5的优先级高于15,那么我们的treap就做出如下调整:

看过这个例子后我们可以知道两件事:

①。加上了优先级这个东西,并不会破坏原有的排序树的性质。就上例来说,4、5、15本来就在排序树上有好几种写法,调整可以看成是换了种写法。

②。剧透一下,每个节点上的优先级都是随机生成的,所以treap的复杂度叫做"期望复杂度",顾名思义就是treap仍有可能退化成一条链。比如上例的5的优先级没有大于15的话,那就是条链了。不过基本上treap的性能还是挺好的(一条链的意思是每个节点的优先级不断减小,这概率实在是不怎么大吧?)。

先放上结构体,就是在排序树的struct中放个优先级和其他一点小操作就行了

struct node
{
	int val; // 值
	int pri; // 优先级
	int size;// 树的大小(左子树数量+右子树数量+1(自身))
	node *ch[2];
	node(int v, node *n):val(v) // 初始化结点
	{
		ch[0] = ch[1] = n;
		pri = rand();
		size = 1;
	}
	void calsize()  // 计算树的大小
	{
		size = ch[0]->size + ch[1]->size + 1;
	}
};

接下来看看treap要如何操作,它的精华就是旋转,还是以上面的4、5、15为例,我们就先来看看如何旋转。

代码肯定是这样的:

// 这个节点的孩子节点的优先级大于根节点,那么就要旋转,上例来说是左孩子优先级大于根节点

if(node->leftChild->priority > node->priority)

    rot(node, left);

那么再来看看rot()的代码如何来写,上例的旋转叫做“右旋”,还有个叫"左旋",我们画个抽象的图来表达一下:

上图中红色的结点代表了即将旋转到上面去的结点。圆和三角形没有任何区别,纯粹是为了连线上更加便于理解而已。

具体来看一下右旋吧:

其实也就改变了两个指向而已。

void rot() // 假设根节点是t,待旋转结点是y,右旋
{
	node *y = t->ch[0];
	t->ch[0] = y->ch[1];
	y->ch[1] = t;
	t = y;
}

最后为什么会有个t = y呢,原因很简单,比如之前的4、5、15,旋转以后的根节点就不是15了而是5了,如果旋转以后不把根节点也更新了当然就出错了。

看到这里希望大家能自行写出左旋的代码,几乎是一样的。

既然左旋右旋代码几乎一样,我们就合并在一个函数里面好了

void rot(node *&t, bool d) // *&是引用,这样在调用rot()的时候就能把形参那边的根节点标成正确的根节点了
{
	node *r = t->ch[d];
	t->ch[d] = r->ch[!d];
	r->ch[!d] = t;
	t->calsize(); // 计算t的子节点数, 先略过
	r->calsize(); // 计算r的子节点数
	t = r;
}
treap的旋转操作基本上讲完了,treap的代码非常简单,重点就在于理解。

接下来是往treap插入结点、删除结点、查找等等,我将放到下一篇博客中。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值