Python利用treap实现双索引的方法
作者:陈屹 发布时间:2022-02-28 15:59:44
前言:
在很多应用场景下,我们不但需要堆的特性,例如快速知道数据最大值或最小值,同时还需要知道元素的排序信息,因此本节我们看看如何实现鱼和熊掌如何兼得。假设我们有一系列数据,它的元素由两部分组成,一部分对应商品的名称,其类型为字符串,一部分对应商品的货存数量,类型为整形,我们既需要将商品根据其名称排序,同时我们又需要快速查询当前货存最小的商品,我们如何设计相应的算法和数据结构来满足这样特性呢。
举个例子,如下图:
从上图看,它对应元素字符串是排序二叉树,因此根节点左子树对应元素的字符串都小于根字符串,同时右子树对应的字符串都大于根节点字符串,同时每个元素还对应着相应商品的货存数量,我们需要及时掌握当前货存最少的商品,这样才能在其耗尽之前迅速补货。但是从上图可以看到,要保证字符串的排序性就得牺牲对于商品数量的小堆性质,例如上图中water对应的货存与wine对应的货存违背了小堆的性质,现在问题是如何在保证字符串排序的情况下,确保数量同时能满足小堆性质。
首先我们先定义一下数据结构:
class Node:
def __init__(self, key: str, priority: float):
self._key = key
self._priority = priority
self._left: Node = None
self._right: Node = None
self._parent: Node = None
@property
def left(self):
return self._left
@property
def right(self):
return self._right
@property
def parent(self):
return self._parent
@left.setter
def left(self, node):
self._left = node
if node is not None:
node.parent = self
@right.setter
def right(self, node):
self._right = node
if node is not None:
node.parent = self
@parent.setter
def parent(self, node):
self._parent = node
def is_root(self) -> bool:
if self.parent is None:
return True
return False
def __repr__(self):
return "({}, {})".format(self._key, self._priority)
def __str__(self):
repr_str: str = ""
repr_str += repr(self)
if self.parent is not None:
repr_str += " parent: " + repr(self.parent)
else:
repr_str += " parent: None"
if self.left is not None:
repr_str += " left: " + repr(self.left)
else:
repr_str += " left: None"
if self.right is not None:
repr_str += " right: " + repr(self.right)
else:
repr_str += " right: None"
return repr_str
class Treap:
def __init__(self):
self.root : Node = None
当前问题是,当上图所示的矛盾出现时,我们如何调整,使得字符串依然保持排序性质,同时货存数值能满足小堆性质。我们需要根据几种情况采取不同操作,首先看第一种,如下图:
从上图看到,一种情况是父节点与左孩子在数值上违背了堆的性质,此时我们执行一种叫右旋转操作,
其步骤是:
Beer
节点逆时针旋转,替换其父节点;父节点
Cabbage
顺时针旋转,成为Beer
的右孩子节点;原来
Beer
的右孩子节点转变为Cabbage
的左孩子节点;
完成后结果如下图所示:
可以看到,此时字符串依然保持排序二叉树性质,同时数值对应的小堆性质也得到了满足。
我们看看代码实现:
class Treap:
def __init__(self):
self._root: Node = None
def right_rotate(self, x: Node):
if x is None or x.is_root() is True:
return
y = x.parent
if y.left != x: # 必须是左孩子才能右旋转
return
p = y.parent
if p is not None: # 执行右旋转
if p.left == y:
p.left = x
else:
p.right = x
else:
self._root = x
y.left = x.right
x.right = y
接下来我们构造一些数据测试一下上面的实现是否正确:
def setup_right_rotate():
flour: Node = Node("Flour", 10)
cabbage: Node = Node("Cabbage", 77)
beer: Node = Node("Beer", 76)
bacon: Node = Node("Bacon", 95)
butter: Node = Node("Butter", 86)
flour.parent = None
flour.left = cabbage
flour.right = None
cabbage.left = beer
beer.left = bacon
beer.right = butter
return flour, beer
def print_treap(n: Node):
if n is None:
return
print(n)
print_treap(n.left)
print_treap(n.right)
treap = Treap()
root, x , cabbage = setup_right_rotate()
print("---------before right rotate---------:")
print_treap(root)
treap.right_rotate(x)
print("-------after right rotate-------")
print_treap(root)
上面代码执行后输出内容如下:
---------before right rotate---------:
(Flour, 10) parent: None left: (Cabbage, 77) right: None
(Cabbage, 77) parent: (Flour, 10) left: (Beer, 76) right: (Eggs, 129)
(Beer, 76) parent: (Cabbage, 77) left: (Bacon, 95) right: (Butter, 86)
(Bacon, 95) parent: (Beer, 76) left: None right: None
(Butter, 86) parent: (Beer, 76) left: None right: None
(Eggs, 129) parent: (Cabbage, 77) left: None right: None
-------after right rotate-------
(Flour, 10) parent: None left: (Beer, 76) right: None
(Beer, 76) parent: (Flour, 10) left: (Bacon, 95) right: (Cabbage, 77)
(Bacon, 95) parent: (Beer, 76) left: None right: None
(Cabbage, 77) parent: (Beer, 76) left: (Butter, 86) right: (Eggs, 129)
(Butter, 86) parent: (Cabbage, 77) left: None right: None
(Eggs, 129) parent: (Cabbage, 77) left: None right: None
对比右旋转前后输出的二叉树看,旋转后的二叉树打印信息的确跟上面我们旋转后对应的图像是一致的。接下来我们实现左旋转,先把上图中cabbage
节点对应的值改成75,这样它与父节点就违背了小堆性质:
我们要做的是:
把
cabbage
节点向“左”旋转到beer
的位置;beer
的父节点设置为cabbage
;beer
的右孩子设置为cabbage
的左孩子;cabbage
的左孩子变成beer
;左旋转后二叉树
成形如下:
从上图看,左旋转后,字符串依然保持二叉树排序性,同时数值的排放也遵守小堆原则,我们看相应的代码实现:
class Treap:
...
def left_rotate(self, x : Node):
if x is None or x.is_root() is True:
return
y = x.parent
if y.right is not x: # 只有右孩子才能左旋转
return
p = y.parent
if p is not None:
if p.left is y:
p.left = x
else:
p.right = x
else:
self._root = x
y.right = x.left
x.left = y
为了测试上面代码实现,我们首先把cabbage
的值修改,然后调用上面代码:
cabbage._priority = 75
print("-------before left rotate--------")
print_treap(root)
treap.left_rotate(cabbage)
print("-------after left rotate---------")
print_treap(root)
代码运行后输出结果为:
-------before left rotate--------
(Flour, 10) parent: None left: (Beer, 76) right: None
(Beer, 76) parent: (Flour, 10) left: (Bacon, 95) right: (Cabbage, 75)
(Bacon, 95) parent: (Beer, 76) left: None right: None
(Cabbage, 75) parent: (Beer, 76) left: (Butter, 86) right: (Eggs, 129)
(Butter, 86) parent: (Cabbage, 75) left: None right: None
(Eggs, 129) parent: (Cabbage, 75) left: None right: None
-------after left rotate---------
(Flour, 10) parent: None left: (Cabbage, 75) right: None
(Cabbage, 75) parent: (Flour, 10) left: (Beer, 76) right: (Eggs, 129)
(Beer, 76) parent: (Cabbage, 75) left: (Bacon, 95) right: (Butter, 86)
(Bacon, 95) parent: (Beer, 76) left: None right: None
(Butter, 86) parent: (Beer, 76) left: None right: None
(Eggs, 129) parent: (Cabbage, 75) left: None right: None
输出结果的描述与上图左旋转后的结果是一致的。由于Treap
相对于元素的key是排序二叉树,因此在给定一个字符串后,我们很容易查询字符串是否在Treap
中,其本质就是排序二叉树的搜索,其实现我们暂时忽略。
虽然查询很简单,但是插入节点则稍微麻烦,因为插入后,新节点与其父节点可能会违背小堆性质,因此在完成插入后,我们还需使用上面实现的左旋转或右旋转来进行调整。
来源:https://developer.51cto.com/art/202109/681869.htm
猜你喜欢
- 一、前言作为一个数据库爱好者,自己动手写过简单的SQL解析器以及存储引擎,但感觉还是不够过瘾。<<事务处理-概念与技术>&
- 整体思路将要备份的目录列为一个列表,通过执行系统命令,进行压缩、备份。这样关键在于构造命令并使用 os.system( )来执行,一开始使用
- 需要分件html源代码 此例中的被抓取的html源代码如下 <p align=left>2004年8月24日星期二;白天:晴有时
- 如下所示:interval=stats.t.interval(a,b,mean,std)t分布的置信区 间a:置信水平b:检验量的自由度me
- 首先看一下目标的验证形态是什么样子的是一种通过验证推理的验证方式,用来防人机破解的确是很有效果,但是,But,这里面已经会有一些破绽,比如:
- 1. 概念map函数也是python中的一个内置函数,用法同之前讲过的filter函数类似。map在这里的意思是映射的意思,会根据提供的函数
- 有时候需要比较大的计算量,这个时候Python的效率就很让人捉急了,此时可以考虑使用numba 进行加速,效果提升明显~(numba 安装貌
- 前言由于今年暑假在学习一些自然语言处理的东西,发现网上对k-means的讲解不是很清楚,网上大多数代码只是将聚类结果以图片的形式呈现,而不是
- 先了解什么是deferGo语言中的defer与return执行的先后顺序Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 d
- 调用数据库存储过程见下:<%Set Dataconn = Server.CreateObject(&qu
- asp十进制转二进制;二进制转十进制;二进制转十六进制;十六进制转二进制;八进制转二进制'二进制转八进制;八进制转十进制;十六进制转
- 系列教程MySQL系列之开篇 MySQL关系型数据库基础概念 MySQL系列之一 MariaDB-server安装 MySQL系列之二 多实
- 本文实例讲述了python自动翻译实现方法。分享给大家供大家参考,具体如下:以前学过python的基础,一般也没用过。后来有一个参数表需要中
- 根据教程实现了读取csv文件前面的几行数据,一下就想到了是不是可以实现前面几列的数据。经过多番尝试总算试出来了一种方法。之所以想实现读取前面
- 1. 认识数据库1.1 数据库和数据结构的关系数据结构:是指相互之间存在一种或多种特定关系的数据元素的集合,是一个抽象的学科我们熟知的数据结
- 程序设计中三种基本机构是顺序结构、选择结构和循环结构。顺序结构语句是程序中最基础的语句,赋值语句、输入/输出语句、模块导入语句等都是顺序结构
- 如下所示:function makeAcquire($nUsers,$nAwards) { &
- 本文章来为各位介绍一个python的例子,这个就是bootstrap+flask写登录页面的例子,希望文章能够对各位有所帮助。Flask是一
- TFTP文件传输功能:1、获取文件列表2、上传文件3、下载文件4、退出第一部分,TftpServer部分。①导入相关模块from socke
- 前言replace into平时在开发中很少用到,这次是因为在做一个生成分布式ID的开源项目,调研雅虎推出的一个基于数据库生成唯一id生成方