python实现堆和索引堆的代码示例
作者:黄天浩 发布时间:2021-09-21 21:42:05
堆是一棵完全二叉树。堆分为大根堆和小根堆,大根堆是父节点大于左右子节点,并且左右子树也满足该性质的完全二叉树。小根堆相反。可以利用堆来实现优先队列。
由于是完全二叉树,所以可以使用数组来表示堆,索引从0开始[0:length-1]。结点i的左右子节点分别为2i+1,2i+2。长度为length的树的最后一个非叶子节点为length//2-1。当前节点i的父节点为(i-1)//2。其中//表示向下取整。
以大根堆举例。当每次插入或者删除的时候,为了保证堆的结构特征不被破坏,需要进行调整。调整分为两种,一种是从上往下,将小的数下沉。一种是从下往上,令大的数上浮。
具体实现如下:
首先编写几个魔术方法。包括构造函数,可以直接调用len来返回data数组长度的函数,一个打印data内容的函数
def __init__(self, data=[]):
self.data = data
self.construct_heap()
def __len__(self):
return len(self.data)
def __str__(self):
return str(self.data)
定义一个swap函数,来方便的交换数组中两个索引处的值。
def swap(self, i, j):
self.data[i], self.data[j] = self.data[j], self.data[i]
定义float_up方法,使堆中大的数能浮上来。当前节点不为根节点,并且当前节点数据大小大于父节点时,上浮。
def float_up(self, i):
while i > 0 and self.data[i] > self.data[(i - 1) // 2]:
self.swap(i, (i - 1) // 2)
i = (i - 1) // 2
定义sink_down方法,使堆中小的数沉下去。当前节点不为叶子节点时,如果小于左孩子或右孩子的数据,则和左右孩子中较大的换一下位置。
def sink_down(self, i):
while i < len(self) // 2:
l, r = 2 * i + 1, 2 * i + 2
if r < len(self) and self.data[l] < self.data[r]:
l = r
if self.data[i] < self.data[l]:
self.swap(i, l)
i = l
实现append方法,能够动态地添加数据。在数据数组尾部添加数据,然后将数据上浮。
def append(self, data):
self.data.append(data)
self.float_up(len(self) - 1)
实现pop_left方法,取堆中最大元素,即优先队列中第一个元素。将数组中第一个元素与最后一个元素换位置,删除最后一个元素,然后将第一个元素下沉到合适的位置。
def pop_left(self):
self.swap(0, len(self) - 1)
r = self.data.pop()
self.sink_down(0)
return r
如果想在初始化堆的时候,向构造函数中传入数据参数,则需要一次性将整个堆构建完毕,而不能一个一个加入。实现也很简单,从最后一个非叶节点开始,逐个执行sink_down操作。
def construct_heap(self):
for i in range(len(self) // 2 - 1, -1, -1):
self.sink_down(i)
这样一个基本的堆的代码就编写完毕了。
但是如果我们想要动态的改变数据,当前的堆就不能满足我们的需求了,因为索引不能总是标识同一个数据,因为堆的结构是不断调整的。我们需要使用索引堆。
在索引堆中,我们不在堆中直接保存数据,而是用在堆中存放数据的索引。
如果我们输入的数据arr是 45 20 12 5 35。则arr[0]一直指向45,arr[1]一直指向20,因为我们在调整堆结构中实际调整的是索引数组,而不会改变真实存放数据的数组。
因此我们的代码需要调整,首先在构造函数中加入一个索引数组。下标从0开始,与存放数据的数组的下标相对应。
def __init__(self, data=[]):
self.data = data
self.index_arr = list(range(len(self.data)))
self.construct_heap()
然后将返回堆长度的魔术函数也修改一下。
def __len__(self):
return len(self.index_arr)
调整一下之前定义的swap方法,原来是直接交换数据,现在交换索引。
def swap(self, i, j):
self.index_arr[i], self.index_arr[j] = self.index_arr[j], self.index_arr[i]
调整float_up以及sink_down中的相应位置
def float_up(self, i):
while i > 0 and self.data[self.index_arr[i]] > self.data[self.index_arr[(i - 1) // 2]]:
self.swap(i, (i - 1) // 2)
i = (i - 1) // 2
def sink_down(self, i):
while i < len(self) // 2:
l, r = 2 * i + 1, 2 * i + 2
if r < len(self) and self.data[self.index_arr[l]] < self.data[self.index_arr[r]]:
l = r
if self.data[self.index_arr[i]] < self.data[self.index_arr[l]]:
self.swap(i, l)
i = l
当append数据的时候,要相应的更新index_arr
def append(self, data):
self.data.append(data)
self.index_arr.append(len(self))
self.float_up(len(self) - 1)
当移出数据的时候,之前已经提到过存放数据的数组,是按照append的顺序进行存储的,平时操作只是对index_arr的顺序进行调整。
如果data_arr为 42 30 74 60 相应的index_arr应该为2 3 0 1
这时,当我们popleft出最大元素时,data_arr中的74被移出后变成了42 30 60,数组中最大索引由3变成了2,如果索引数组中仍然用3这个索引来索引30会造成index溢出。74的索引为2,需要我们将索引数在2之后的都减1。
综上,在删除元素时,我们原先是将data_arr中的首尾元素互换,再删除尾部元素,再对头部元素进行sink_down操作。现在我们先换索引数组中首尾元素,再删除索引数组尾部元素,此时尚未操作存放data的data_arr,因此索引数组剩余元素与data_arr的元素仍是一一对应的。进行sink_down操作,操作完成之后再删除data_arr相应位置元素。最后将index_arr中值大于原index_arr头部元素值的减一。
def pop_left(self):
self.swap(0, len(self) - 1)
r = self.index_arr.pop()
self.sink_down(0)
self.data.pop(r)
for i, index in enumerate(self.index_arr):
if index > r:
self.index_arr[i] -= 1
return r
索引堆增加了一个更新操作,可以随时更新索引堆中的数据。更新时,先直接更新data_arr中相应索引处的数据,然后在index_arr中,找到存放了data_arr中,刚被更新的数据的索引的索引位置,与删除时一样需要进行一次遍历。找到这个位置之后,由于无法确定与前后元素的大小关系,因此需要进行一次float_up操作再进行一次sink_down操作。
def update(self, i, data):
self.data[i] = data
for index_index, index in enumerate(self.index_arr):
if index == i:
target = index_index
self.float_up(target)
self.sink_down(target)
可以很明显看出,这个索引堆在插入元素时是比较快的,但是在删除元素和更新元素时,为了查找相应位置索引,都进行了一次遍历,这是很耗时的操作。为了能更快的找到index_arr中值为要更新的data_arr的相应索引值得索引位置,我们再次开辟一个新的数组to_index,来对index_arr进行索引。
例如对于数组75 54 65 90
此时它的index_arr为3 0 2 1。当要更新data[3],即90这个元素时,现在要遍历一遍index_arr来找到3这个位置,这个位置是0。我们要建立一个to_index,to_index[3]中存放的元素为0。
index_arr存放的元素分别为: 1 3 2 0。
先改变swap数组,在交换index_arr中元素时,也交换存放在to_index中的index_arr的索引。
def swap(self, i, j):
self.index_arr[i], self.index_arr[j] = self.index_arr[j], self.index_arr[i]
self.to_index[self.index_arr[i]], self.to_index[self.index_arr[j]] = self.to_index[self.index_arr[j]], \
self.to_index[self.index_arr[i]]
然后在update中,当要更新位置为i的元素时,我们就不需要通过一次遍历才能找到index_arr中该元素的索引,而是直接通过访问index_arr[i]即可访问index_arr中相应索引
def update(self, i, data):
self.data[i] = data
target = self.to_index[i]
self.float_up(target)
self.sink_down(target)
最后改变pop_left中相应代码,这时我们需要维护三个数组,data_arr,index_arr以及to_index。
仍然是首先将index_arr首位元素交换,并pop出尾部元素存放到i中。然后将头部元素sink_down到相应位置,然后将pop出data_arr索引i处的元素。然后pop出to_index中索引为i的元素,再将index_arr中索引溢出的元素进行调整。
def pop_left(self):
self.swap(0, len(self) - 1)
r = self.index_arr.pop()
self.sink_down(0)
self.data.pop(r)
self.to_index.pop(r)
for i in range(r, len(self)):
self.index_arr[self.to_index[i]] -= 1
return r
来源:https://segmentfault.com/a/1190000013774311


猜你喜欢
- 假设我们有这样一种数据:data = [ ("apple&quo
- 上篇文章讲了js中的一些概念(词法结构) 和 数据类型(部分)。这章我们 继续.然后了解下js中操作数据 和 函数的 作用域。1,对象跟基本
- 如何为XHTML做好准备,XHTML与HTML 4.01标准没有太多的不同。所以将你的代码升级至4.01是个不错的开始。HTML 4.01参
- 欣赏上一篇:用画为5.12地震受灾同胞们祈福 今年我们的祖国多灾多难 雪灾的阴影还没散去又发生了地震。中国插画 * 举办5.12地震祈幅绘画活
- 这个concatenate用于将矩阵合并,他将沿着已经存在的轴合并一个矩阵,相关参数有(a1, a2, ...), axis=0, out=
- 由于asp中是使用双引号作为字符串的开始和结束标志的,单一个字符串中的双引号出现次数大于两个时,程序就有可能运行错误。asp中是怎么输出引号
- 1.对于一维数组,可以有:2. 对于二维数组:考虑可将其看作为矩阵,故可以如下书写二重遍历 这里外层循环的是二维数组A的行,内层则
- 在新版的Vue CLI 3中,如何导入公共less文件在文档里已经描述的很清楚了,但是在2.*的版本中,我没有查到相关的办法,网友的办法又相
- QQ影音新版发布官网Banner经过两周的酝酿、脑爆与设计调整,于20日顺利上线,连续7天,经历了昨天激动人心的最后发布,到此告一段落,这里
- 前言因为经常一训练就是很多次迭代,所以找到效率比较高的操作能大大缩短运行时间,但这方面资料不足,所以自己记录总结一下,有需要再补充索引效率与
- 这次讨论一下关于select元素的一个问题,其实很早以前我就碰到过关于select元素的问题,这次做网站又被问到同样的问题,就是:一般div
- 环境: Python3 + windows。开发工具:Anaconda + Jupyter / VS Code。学习效果:1.认识爬虫 /
- 本文实例为大家分享了40行Python代码实现计算器功能,供大家参考,具体内容如下偶尔用脚本写点东西也是不错的。效果图代码from tkin
- PyCharm自身提供了大量实用的快捷键,但是由于自己之前其他软件的快捷键使用习惯与此不同,这就需要在PyCharm量身DIY属于自己的快捷
- 实现神经网络的权重和偏置更新,很重要的一部就是使用BackPropagation(反向传播)算法。具体来说,反向传播算法就是用误差的反向传播
- 1,复制简介 简单的说,复制是获取一个或多个数据库的过程,它系统的针对出入不同数据库的数据,提供基于规则的拷贝机制。 复制分为三种角色, 1
- 一、打包多个1、将需要打包的项目为anjuke_sd目录下的所有python文件,其中excute_main.py为主文件。2、生成主函数对
- //比较数组是否相同 modeler.compArray=function(array1,array2) { &nb
- 成功解决ValueError: Supported target types are: ('binary', 'mu
- 一、提前准备为了大家学习方便,我在这里面建立两张表并为其添加一些数据。一张水果表,一张供应商表。水果表 fruits表f_idf_namef