大家好,今天和大家聊一个新的数据结构,叫做Treap。

Treap本质上也是一颗BST(平衡二叉搜索树),和我们之前介绍的SBT是一样的。但是Treap维持平衡的方法和SBT不太一样,有些许区别,相比来说呢,Treap的原理还要再简单一些,所以之前在竞赛当中不允许使用STL的时候,我们通常都会手写一棵Treap来代替。

Treap的基本原理

既然是平衡二叉搜索树,关键点就在于平衡,那么重点自然是如何维护树的平衡。

在Treap当中,维护平衡非常简单,只有一句话,就是通过维护小顶堆的形式来维持树的平衡。Treap也正是因此得名,因为它是Tree和Heap的结合体。

我们来看下Treap当中节点的结构:

classTreapNode(TreeNode):

"""

TreeNode:Thenodeclassoftreaptree.

Paramters:

key:Thekeyofnode,canbetreatedasthekeyofdictionary

value:Thevalueofnode,canbetreatedasthevalueofdictionary

priority:Thepriorityofnode,speciallyfortreapstructure,describethepriorityofthenodeinthetreap.

lchild:Theleftchildofnode

rchild:Therightchildofnode

father:Theparentofnode,incasethatweneedtoremoveorrotatethenodeinthetreap,soweneedfatherparametertomarktheaddressoftheparent

"""

def__init__(self,key=None,value=None,lchild=None,rchild=None,father=None,priority=None):

super().__init__(key,value,lchild,rchild,father)

self._priority=priority

@property

defpriority(self):

returnself._priority

@priority.setter

defpriority(self,priority):

self._priority=priority

def__str__(self):

return'key={},value={}'.format(self.key,self.value)

这里的TreeNode是我抽象出来的树结构通用的Node,当中包含key、value、lchild、rchild和father。TreapNode其实就是在此基础上增加了一个priority属性。

之所以要增加这个priority属性是为了维护它堆的性质,通过维护这个堆的性质来保持树的平衡。具体的操作方法,请往下看。

Treap的增删改查

插入

首先来讲Treap的插入元素的操作,其实插入元素的操作非常简单,就是普通BST插入元素的操作。唯一的问题是如何维持树的平衡。

我们前文说了,我们是通过维持堆的性质来保持平衡的,那么自然又会有一个新的问题。为什么维持堆的性质可以保证平衡呢

答案很简单,因为我们在插入的时候,需要对每一个插入的Node随机附上一个priority。堆就是用来维护这个priority的,保证树根一定拥有最小的priority。正是由于这个priority是随机的,我们可以保证整棵树蜕化成线性的概率降到无穷低。

当我们插入元素之后发现破坏了堆的性质,那么我们需要通过旋转操作来维护。举个简单的例子,在下图当中,如果B节点的priority比D要小,为了保证堆的性质,需要将B和D进行互换。由于直接互换会破坏BST的性质,所以我们采取旋转的操作。

旋转之后我们发现B和D互换了位置,并且旋转之后的A和E的priority都是大于D的,所以旋转之后我们整棵树依然维持了性质。

右旋的情况也是一样的,其实我们观察一下会发现,要交换左孩子和父亲需要右旋,如果是要交换右孩子和父亲,则需要左旋。

整个插入的操作其实就是基础的BST插入过程,加上旋转的判断。

def_insert(self,node,father,new_node,left_or_right='left'):

"""

Insideimplementofinsertnode.

Implementinrecursion.

SincetheparameterpassedinPythonisreference,sowhenweaddnode,weneedtoassignthenodetoitsfather,otherwisethereferencewillloseoutsidethefunction.

Whenweaddnode,weneedtocompareitskeywithitsfather'skeytomakesureit'sthelchildorrchildofitsfather.

"""

ifnodeisNone:

ifnew_node.keyfather.key:

father.lchild=new_node

else:

father.rchild=new_node

new_node.father=father

return

ifnew_node.keynode.key:

self._insert(node.lchild,node,new_node,'left')

#maintain

ifnode.lchild.prioritynode.priority:

self.rotate_right(node,father,left_or_right)

else:

self._insert(node.rchild,node,new_node,'right')

#maintain

ifnode.rchild.prioritynode.priority:

self.rotate_left(node,father,left_or_right)

前面的逻辑就是BST的插入,也就是和当前节点比大小,决定插入在左边还是右边。注意一下,这里我们在插入完成之后,增加了maintain的逻辑,其实也就是比较一下,刚刚进行的插入是否破坏了堆的性质。可能有些同学要问我了,这里为什么只maintain了一次有可能插入的priority非常小,需要一直旋转到树根不是吗

的确如此,但是不要忘了,我们这里的maintain逻辑并非只调用一次。随着整个递归的回溯,在树上的每一层它其实都会执行一次maintain逻辑。所以是可以保证从插入的地方一直维护到树根的。

查询

查询很简单,不用多说,就是BST的查询操作,没有任何变化。

def_query(self,node,key,backup=None):

ifnodeisNone:

returnbackup

ifkeynode.key:

returnself._query(node.lchild,key,backup)

elifkeynode.key:

returnself._query(node.rchild,key,backup)

returnnode

defquery(self,key,backup=None):

"""

Returntheresultofqueryaspecificnode,ifnotexistsreturnNone

"""

returnself._query(self.root,key,backup)

删除

删除的操作稍微麻烦了一些,由于涉及到了优先级的维护,不过逻辑也不难理解,只需要牢记需要保证堆的性质即可。

首先,有两种情况非常简单,一种是要删除的节点是叶子节点,这个都很容易想明白,删除它不会影响任何其他节点,直接删除即可。第二种情况是链节点,也就是说它只有一个孩子,那么删除它也不会引起变化,只需要将它的孩子过继给它的父亲,整个堆和BST的性质也不会受到影响。

对于这两种情况之外,我们就没办法直接删除了,因为必然会影响堆的性质。这里有一个很巧妙的做法,就是可以先将要删除的节点旋转,将它旋转成叶子节点或者是链节点,再进行删除。

在这个过程当中,我们需要比较一下它两个孩子的优先级,确保堆的性质不会受到破坏。

def_delete_node(self,node,father,key,child='left'):

"""

Implementfunctionofdeletenode.

Definedasaprivatefunctionthatonlycanbecalledinside.

"""

ifnodeisNone:

return

ifkeynode.key:

self._delete_node(node.lchild,node,key)

elifkeynode.key:

self._delete_node(node.rchild,node,key,'right')

else:

#如果是链节点,叶子节点的情况也包括了

ifnode.lchildisNone:

self.reset_child(father,node.rchild,child)

elifnode.rchildisNone:

self.reset_child(father,node.lchild,child)

else:

#根据两个孩子的priority决定是左旋还是右旋

ifnode.lchild.prioritynode.rchild.priority:

node=self.rotate_right(node,father,child)

self._delete_node(node.rchild,node,key,'right')

else:

node=self.rotate_left(node,father,child)

self._delete_node(node.lchild,node,key)

defdelete(self,key):

"""

Interfaceofdeletemethodfaceoutside.

"""

self._delete_node(self.root,None,key,'left')

修改

修改的操作也非常简单,我们直接查找到对应的节点,修改它的value即可。

旋转

我们也贴一下旋转操作的代码,其实这里的逻辑和之前SBT当中介绍的旋转操作是一样的,代码也基本相同:

defreset_child(self,node,child,left_or_right='left'):

"""

Resetthechildoffather,sinceinPythonalltheinstancespassedbyreference,soweneedtosetthenodeasachildofitsfathernode.

"""

ifnodeisNone:

self.root=child

self.root.father=None

return

ifleft_or_right=='left':

node.lchild=child

else:

node.rchild=child

ifchildisnotNone:

child.father=node

defrotate_left(self,node,father,left_or_right):

"""

LeftrotateoperationofTreap.

Example:

D

/\

AB

/\

EC

Afterrotate:

B

/\

DC

/\

AE

"""

rchild=node.rchild

node.rchild=rchild.lchild

ifrchild.lchildisnotNone:

rchild.lchild.father=node

rchild.lchild=node

node.father=rchild

self.reset_child(father,rchild,left_or_right)

returnrchild

defrotate_right(self,node,father,left_or_right):

"""

RightrotateoperationofTreap.

Example:

D

/\

AB

/\

EC

Afterrotate:

A

/\

ED

/\

CB

"""

lchild=node.lchild

node.lchild=lchild.rchild

iflchild.rchildisnotNone:

lchild.rchild.father=node

lchild.rchild=node

node.father=lchild

self.reset_child(father,lchild,left_or_right)

returnlchild

这里唯一要注意的是,由于Python当中存储的都是引用,所以我们在旋转操作之后必须要重新覆盖一下父节点当中当中的值才会生效。负责我们修改了node的引用,但是father当中还是存储的旧的地址,一样没有生效。

后记

基本上到这里整个Treap的原理就介绍完了,当然除了我们刚才介绍的基本操作之外,Treap还有一些其他的操作。比如可以split成两个Treap,也可以由两个Treap合并成一个。还可以查找第K大的元素,等等。这些额外的操作,我用得也不多,就不多介绍了,大家感兴趣可以去了解一下。

Treap这个数据结构在实际当中几乎没有用到过,一般还是以竞赛场景为主,我们学习它主要就是为了提升和锻炼我们的数据结构能力以及代码实现能力。Treap它的最大优点就是实现简单,没有太多复杂的操作,但是我们前面也说了,它是通过随机的priority来控制树的平衡的,那么它显然无法做到完美平衡,只能做到不落入最坏的情况,但是无法保证可以进入最好的情况。不过对于二叉树来说,树深的一点差距相差并不大。所以Treap的性能倒也没有那么差劲,属于一个性价比非常高的数据结构。

最后,还是老规矩,我把完整的代码放在了paste当中,大家感兴趣可以点击阅读原文查看,代码里都有详细的注释,大家应该都能看明白。

今天的文章就到这里,衷心祝愿大家每天都有所收获。如果还喜欢今天的内容的话,请来一个三连支持吧~(点赞、关注、转发)

java sbt二叉树,Treap——堆和二叉树的完美结合,性价比极值的搜索树相关推荐

  1. 数据结构(三):非线性逻辑结构-特殊的二叉树结构:堆、哈夫曼树、二叉搜索树、平衡二叉搜索树、红黑树、线索二叉树

    在上一篇数据结构的博文<数据结构(三):非线性逻辑结构-二叉树>中已经对二叉树的概念.遍历等基本的概念和操作进行了介绍.本篇博文主要介绍几个特殊的二叉树,堆.哈夫曼树.二叉搜索树.平衡二叉 ...

  2. 【Java数据结构与算法】第十一章 顺序存储二叉树、线索二叉树和堆

    第十一章 顺序存储二叉树.线索化二叉树.大顶堆.小顶堆和堆排序 文章目录 第十一章 顺序存储二叉树.线索化二叉树.大顶堆.小顶堆和堆排序 一.顺序存储二叉树 1.介绍 2.代码实现 二.线索二叉树 1 ...

  3. java 递归深度优先遍历_Java基础 - 二叉树的遍历之深度优先遍历(递归遍历)

    package com.yc.test; import java.util.ArrayList; import java.util.List; import com.yc.tree.ThreeLink ...

  4. 数据结构与算法——二叉树、堆、优先队列

    *************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 七 ...

  5. java二叉树的序列化_二叉树的序列化和反序列化

    import java.util.LinkedList; import java.util.Queue; /** * 序列化和反序列化二叉树 * 先序.后序 * (中序不能实现) * 按层 */ pu ...

  6. DSt:数据结构的最强学习路线之数据结构知识讲解与刷题平台、刷题集合、问题为导向的十大类刷题算法(数组和字符串、栈和队列、二叉树、堆实现、图、哈希表、排序和搜索、动态规划/回溯法/递归/贪心/分治)总

    DSt:数据结构的最强学习路线之数据结构知识讲解与刷题平台.刷题集合.问题为导向的十大类刷题算法(数组和字符串.栈和队列.二叉树.堆实现.图.哈希表.排序和搜索.动态规划/回溯法/递归/贪心/分治)总 ...

  7. 二叉树的讲解《二》(二叉树实现堆)

     个人主页:欢迎大家光临-->沙漠下的胡杨   各位大帅哥,大漂亮  如果觉得文章对自己有帮助  可以一键三连支持博主  你的每一分关心都是我坚持的动力   ☄: 本期重点:二叉树实现堆  希望 ...

  8. 数据结构(Java实现)-详谈树与二叉树结构

    1.树 1.1树的基本介绍 树(tree)是一种抽象数据类型(ADT),用来模拟具有树状结构性质的数据集合.它是由n(n>0)个有限节点通过连接它们的边组成一个具有层次关系的集合.把它叫做&qu ...

  9. 剑指offer编程题(JAVA实现)——第38题:二叉树的深度

    github https://github.com/JasonZhangCauc/JZOffer 剑指offer编程题(JAVA实现)--第38题:二叉树的深度 题目描述 输入一棵二叉树,求该树的深度 ...

最新文章

  1. WEB打印大全(转)
  2. NYOJ 309 BOBSLEDDING(dp)
  3. 为什么Redis是单线程?为什么能处理大并发量?(举例不错)
  4. 实时搜索 elasticsearch vs solr
  5. SAP UI5 初学者教程之三:开始接触第一个 SAP UI5 控件 试读版
  6. MySQL5.6主从复制(读写分离)方案
  7. c语言计算火车的运行时间_C++火车入轨算法的实现代码
  8. MFC实现BCB中的ProcessMessages
  9. Tutorial of Codeforces Round 729 (Div.2)
  10. 如何在 Deno 中构建一个 URL 短链生成器
  11. linux 卸载 1.6,在linux上卸载nump1.6.1并安装nump1.5.1,[它将要使用gipsyoasi II version6]...
  12. lvuaagentinstbaseroot_桌面小助手UniAgent删除指南
  13. DNS域名服务器的搭建
  14. 地理信息系统(GIS)的发展历程
  15. 解决Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin
  16. Oracle日志挖掘技术logminer
  17. python考试搜题神器_智慧职教云课堂2020Python程序设计答案搜题公众号
  18. stm32正常运行流程图_stm32初始化流程图解析
  19. ffmpeg获取音频信息
  20. 百度网盘安装在c盘显示系统权限限制的解决方法

热门文章

  1. Codeforces Round #360 (Div. 2) C. NP-Hard Problem 水题
  2. 安装qt5后编译运行后有关Qt at-spi的警告
  3. 不能不说的C#特性-迭代器(下),yield以及流的延迟计算
  4. Nokia防火墙配置过程
  5. 32muduo_net库源码分析(八)
  6. 不抛出异常的swap
  7. oracle 监听报错,解决Oracle监听服务报错
  8. gprs模块http mqtt_GPRS模块用TCP实现MQTT协议(基于SIM900A)
  9. python新建文件夹口令_Python脚本破解压缩文件口令实例教程(zipfile)
  10. ad导入pcd后网络标号消失_如何将后端BaaS化:业务逻辑的拆与合