查找——图文翔解Treap(树堆)

本文深入讲解Treap树的原理及应用,包括其插入、删除、查找等操作,并对比了Splay树、AVL树和红黑树等平衡二叉搜索树。此外,还介绍了如何利用Treap树解决实际问题。

之前我们讲到二叉搜索树,从二叉搜索树到2-3树到红黑树到B-树。
二叉搜索树的主要问题就是其结构与数据相关,树的深度可能会非常大,Treap树就是一种解决二叉搜索树可能深度过大的还有一种数据结构。

Treap

Treap=Tree+Heap。

Treap本身是一棵二叉搜索树,它的左子树和右子树也各自是一个Treap。和一般的二叉搜索树不同的是。Treap纪录一个额外的数据,就是优先级。Treap在以关键码构成二叉搜索树的同一时候,还满足堆的性质。这些优先级是是在结点插入时,随机赋予的。Treap依据这些优先级满足堆的性质。这种话,Treap是有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树

其基本操作的期望时间复杂度为O(logn)。相对于其它的平衡二叉搜索树,Treap的特点是实现简单。且能基本实现随机平衡的结构。


Treap维护堆性质的方法仅仅用到了旋转。仅仅须要两种旋转。编程复杂度比Splay要小一些。


插入 

给节点随机分配一个优先级,先和二叉搜索树的插入一样,先把要插入的点插入到一个叶子上,然后 跟维护堆一样。假设当前节点的优先级比根大就旋转,假设 当前节点是根的左儿子就右旋,假设当前节点是根的右儿子就左旋

以下图解旋转操作:


因为旋转是O(1)的。最多进行h次(h是树的高度)。插入的复杂度是O(h)的。在期望情况下h=O(logn)。所以它的期望复杂度是O(logn)。 
 
以下我们以一个 实例来图解Treap的插入过程。看完之后你一定对Treap的插入了然于胸。






删除

删除一个节点有两种方式。能够像删除二叉树节点那样删除一个节点。也能够像删除堆中节点那样删除。

1、用二叉搜索树的方式删除
先在二叉查找树中找到要删除的节点的位置,然后依据节点分下面情况:
情况一:
该节点是 叶节点(没有非空子节 点的节点),直接把节点删除就可以。

情况二:
该节点是 链节点(仅仅有一个非空子节点的节点),为了删除这个节点而不影响它的子树,须要把它的子节点取代它的位置,然后把它删除。



情况三:
该节点 有两个非空子节点。因为情况比較复杂,一般的策略是 用它右子树的最小值来取代它,然后把它删除。如图所看到的,删除节点2时,在它的右子树中找到最小的节点3, 该节点一定为待删除节点的后继。删除节点3(它可能是叶节点或者链节点),然后把节点2的值改为3。
(当然,我们也能够使用结点的左子树的最大值来替代它, 为了不使Treap向一边偏沉,我们能够随机地选取是用后继还是前驱取代它, 并保证两种选择的概率均等。)


关于查找最小值:
基本方法就是从子树的根节点開始, 假设左子节点不为空,那么就訪问左子节点,直到左子节点为空,当前节点就是该子树的最 小值节点。删除它仅仅需用它的右子节点取代它本身。


2、用堆的方式删除

由于Treap满足堆性质,所以仅仅须要把要删除的节点旋转到叶节点上。然后直接删除就能够了。
详细的方法:
假设该节点的左子节点的优先级小于右子节点的优先级。右旋该节点,使该节点降为右子树的根节点,然后訪问右子树的根节点,继续操作。

反之,左旋该节点,使该节点降为左子树的根节点,然后訪问左子树的根节点。继续操作,直到变成能够直接删除的节点。
(即:让小优先级的结点旋到上面,满足堆的性质)


删除最多进行O(h)次旋转,期望复杂度是O(logn)。


查找

和一般的二叉搜索树一样。可是因为Treap的随机化结构,Treap中查找的期望复杂度是O(logn)。

 


对照 

与 Splay树 相比:
Splay 和 BST 一样,不须要维护不论什么附加域,比 Treap 在空间上有节约。但 Splay 在查找时也会调整结构,这使得 Splay 灵活性稍有欠缺。 Splay 的查找插入删除等基本操作的时间复杂度为均摊O(logN)而非期望。能够有益构造出使 Splay 变得非常慢的数据。

与AVL 红黑树相比:
AVL 和红黑树在调整的过程中,旋转都是均摊 O(1)的,而 Treap 要 O(logN)。

与 Treap 的随机优先级不同,它们维护的附加域要动态的调整,而 Treap 的随机修正值一经生成不再改变,这一点使得灵活性不如 Treap。
AVL 和红黑树都是时间效率非常高的经典算法,在很多专业的应用领域(如 STL)有着十分重要的地位。然而AVL和红黑树的编程实现的难度要比Treap大得多。


维护子树的大小

Treap 是一种排序的数据结构,假设我们想 查找第k小的元素或者 询问某个元素在Treap中从小到大的排名时,我们就必须知道每一个子树中节点的个数。
因为插入、删除、旋转等操作,会使每一个子树的大小改变,所 以我们必须对子树的大小进行动态的维护。

对于 旋转,我们要在旋转后对子节点和根节点分别又一次计算其子树的大小。 
对于 插入,在寻找插入的位置时,每经过一个节点,都要先使以它为根的子树的大小添加 1,再递归进入子树查找。 
对于 删除,在寻找待删除节点,递归返回时要把全部的经过的节点的子树的大小降低 1。

要注意的是,删除之前一定要保证待删除节点存在于 Treap 中。


 
(维护了子树的大小,我们就能够求“排名第k的元素”这种问题了。

快排也能求“第k大”问题,可是快排适合静态的数据,对于常常变动的数据。我们用树结构来维护更灵活。)
(我们还能够求“某个元素的排名”。我们的基本思想是查找目标元素在 Treap 中的位置,且在查找路径中统计出小于目标元素的节点的总个数,目标元素的排名就是总个数+1。即:在查找的路径中统计小于目标元素的个数,当找到目标元素后加上其左子树的个数就可以。)


举个栗子

Treap 是一种高效的动态的数据容器,据此我们能够用它处理一些数据的动态统计问题。

一、一个应用实例
[问题描写叙述] 有一个游戏排名系统,通常要应付三种请求:上传一条新的得分记录、查询某个玩家的当前 排名以及返回某个区段内的排名记录。

当某个玩家上传自己最新的得分记录时,他原有的得 分记录会被删除。

为了减轻server负担,在返回某个区段内的排名记录时,最多返回 10 条 记录。
[求]
(1)更新玩家的得分记录
(2)查询玩家排名(假设两个玩家的得分同样, 则先得到该得分的玩家排在前面。


(3)查询第 Index 名開始的最多 10 名玩家名字

[解]
由于作为字符串的姓名是不便于处理的,我们给每一个玩家都制定一个ID,首先要建立一个由姓名到玩家ID的映射数据结构。

为了查找高速,能够用Trie树。之后我们建立一个双keyword的Treap,keyword1为得分从小到大,keyword2为时间戳从大到小,这样的排列方式的逆序,恰好是我们要的顺序(也能够直接就是逆序)。



对于问题(1),先查询玩家是否已经存在,假设已经存在,在Treap中更新相应已经存在的记录。
对于问题(2),就是主要的求排名操作。
对于问题(3)。就是分别查找第(总记录数 + 1 – k)小的记录。

二、双端优先队列的实现
优先队列(Priority Queue)是一种按优先级维护进出顺序的数据容器结构,能够选择维护实现取出最小值或最大值。我们通经常使用堆实现优先队列,通常取出最值的时间复杂度为 O(logN)。


用最小堆能够实现最小优先队列,用最大堆能够实现最大优先队列。可是假设我们要求一种 “双端优先队列”,即要求同一时候支持插入、取出最大值、取出最小值的操作,用一个单纯的堆就不能高效地实现了。
(能够用两个堆来实现,两堆中的元素都互指,但维护两个堆比較复杂。)

我们能够方便地使用Treap实现双端优先队列,仅仅需建立一个 Treap,分别写出取最大值和最小值的功能代码就能够了, 无需做不论什么改动。

因为Treap平衡性不如堆完美,但期望时间仍是 O(logN)。更重要的是在 实现的复杂程度上大大下降,并且便于其它操作的推广。所以,用 Treap 实现优先队列不失为一种便捷而又灵活的方法。



其他:

平衡树并不适合作为全部数据类型的数据的有序存储容器,由于可能有些类型的两个元素直接相互比較大小是十分 耗时的,这个常数时间的消耗是无法忍受的。比如字符串,作为检索字符串的容器,我们更推荐Trie树,而不是平衡树。平衡树仅适合做元素间相互比較时间非常少的类型的有序存储容器。

关于查找最小值:
基本方法就是从子树的根节点開始, 假设左子节点不为空,那么就訪问左子节点,直到左子节点为空,当前节点就是该子树的最 小值节点。删除它仅仅需用它的右子节点取代它本身。


【參考】

中文维基百科http://zh.wikipedia.org/wiki/%E6%A0%91%E5%A0%86
《随机平衡二叉查找树Treap的分析与应用》 清华大学计算机系 郭家宝



---------------------------------------------
感谢訪问,随手点赞,点滴慈善^_^
---------------------------------------------



转载于:https://www.cnblogs.com/mfrbuaa/p/5203953.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值