python优先队列及二叉堆的实现

发布于 2015-12-18 06:55:17 | 117 次阅读 | 评论: 0 | 来源: PHPERZ

Python编程语言Python 是一种面向对象、解释型计算机程序设计语言,由Guido van Rossum于1989年底发明,第一个公开发行版发行于1991年。Python语法简洁而清晰,具有丰富和强大的类库。它常被昵称为胶水语言,它能够把用其他语言制作的各种模块(尤其是C/C++)很轻松地联结在一起。

优先队列的二叉堆实现

在前面的章节里我们学习了“先进先出”(FIFO)的数据结构:队列(Queue)。队列有一种变体叫做“优先队列”(Priority Queue)。优先队列的出队(Dequeue)操作和队列一样,都是从队首出队。但在优先队列的内部,元素的次序却是由“优先级”来决定:高优先级的元素排在队首,而低优先级的元素则排在后面。这样,优先队列的入队(Enqueue)操作就比较复杂,需要将元素根据优先级尽量排到队列前面。我们将会发现,对于下一章要学的图,优先队列是很有用的数据结构。

我们很自然地会想到用排序算法和队列的方法来实现优先队列。但是,在列表里插入一个元素的时间复杂度是O(n),对列表进行排序的时间复杂度是O(nlogn)。我们可以用别的方法来降低时间复杂度。一个实现优先队列的经典方法便是采用二叉堆(Binary Heap)。二叉堆能将优先队列的入队和出队复杂度都保持在O(logn)。

二叉堆的有趣之处在于,其逻辑结构上像二叉树,却是用非嵌套的列表来实现。二叉堆有两种:键值总是最小的排在队首称为“最小堆(min heap)”,反之,键值总是最大的排在队首称为“最大堆(max heap)”。在这一节里我们使用最小堆。

二叉堆操作

二叉堆的基本操作定义如下:

BinaryHeap():创建一个空的二叉堆对象

insert(k):将新元素加入到堆中

findMin():返回堆中的最小项,最小项仍保留在堆中

delMin():返回堆中的最小项,同时从堆中删除

isEmpty():返回堆是否为空

size():返回堆中节点的个数

buildHeap(list):从一个包含节点的列表里创建新堆

下面所示代码是二叉堆的示例。可以看到无论我们以哪种顺序把元素添加到堆里,每次都是移除最小的元素。我们接下来要来实现这个过程。

from pythonds.trees.binheap import BinHeap

bh = BinHeap()

bh.insert(5)

bh.insert(7)

bh.insert(3)

bh.insert(11)

print(bh.delMin())

print(bh.delMin())

print(bh.delMin())

print(bh.delMin())

二叉堆结构性质

为了更好地实现堆,我们采用二叉树。我们必须始终保持二叉树的“平衡”,就要使操作始终保持在对数数量级上。平衡的二叉树根节点的左右子树的子节点个数相同。在堆的实现中,我们采用“完全二叉树”的结构来近似地实现“平衡”。完全二叉树,指每个内部节点树均达到最大值,除了最后一层可以只缺少右边的若干节点。图 1 所示是一个完全二叉树。

图 1:完全二叉树

有意思的是我们用单个列表就能实现完全树。我们不需要使用节点,引用或嵌套列表。因为对于完全二叉树,如果节点在列表中的下标为 p,那么其左子节点下标为 2p,右节点为 2p+1。当我们要找任何节点的父节点时,可以直接使用 python 的整除。如果节点在列表中下标为n,那么父节点下标为n//2.图 2 所示是一个完全二叉树和树的列表表示法。注意父节点与子节点之间 2p 与 2p+1 的关系。完全树的列表表示法结合了完全二叉树的特性,使我们能够使用简单的数学方法高效地遍历一棵完全树。这也使我们能高效实现二叉堆。

堆次序的性质

我们在堆里储存元素的方法依赖于堆的次序。所谓堆次序,是指堆中任何一个节点 x,其父节点 p 的键值均小于或等于 x 的键值。图 2 所示是具备堆次序性质的完全二叉树。

图 2:完全树和它的列表表示法

二叉堆操作的实现

接下来我们来构造二叉堆。因为可以采用一个列表保存堆的数据,构造函数只需要初始化一个列表和一个currentSize来表示堆当前的大小。Listing 1 所示的是构造二叉堆的 python 代码。注意到二叉堆的heaplist并没有用到,但为了后面代码可以方便地使用整除,我们仍然保留它。

Listing 1

class BinHeap:

def __init__(self):

self.heapList = [0]

self.currentSize = 0

我们接下来要实现的是insert方法。首先,为了满足“完全二叉树”的性质,新键值应该添加到列表的末尾。然而新键值简单地添加在列表末尾,显然无法满足堆次序。但我们可以通过比较父节点和新加入的元素的方法来重新满足堆次序。如果新加入的元素比父节点要小,可以与父节点互换位置。图 3 所示的是一系列交换操作来使新加入元素“上浮”到正确的位置。

图 3:新节点“上浮”到其正确位置

当我们让一个元素“上浮”时,我们要保证新节点与父节点以及其他兄弟节点之间的堆次序。当然,如果新节点非常小,我们仍然需要将它交换到其他层。事实上,我们需要不断交换,直到到达树的顶端。图 3 所示的是“上浮”方法,它把一个新节点“上浮”到其正确位置来满足堆次序。这里很好地体现了我们之前在headlist中没有用到的元素 0 的重要性。这样只需要做简单的整除,将当前节点的下标除以 2,我们就能计算出任何节点的父节点。

在Listing 3 中,我们已经可以写出insert方法的代码。insert里面很大一部分工作是由percUp函数完成的。当树添加新节点时,调用percUp就可以将新节点放到正确的位置上。

Listing 2

def percUp(self,i):

while i // 2 > 0:

if self.heapList[i] < self.heapList[i // 2]:

tmp = self.heapList[i // 2]

self.heapList[i // 2] = self.heapList[i]

self.heapList[i] = tmp

i = i // 2

Listing 3

def insert(self,k):

self.heapList.append(k)

self.currentSize = self.currentSize + 1

self.percUp(self.currentSize)

我们已经写好了insert方法,那再来看看delMin方法。堆次序要求根节点是树中最小的元素,因此很容易找到最小项。比较困难的是移走根节点的元素后如何保持堆结构和堆次序,我们可以分两步走。首先,用最后一个节点来代替根节点。移走最后一个节点保持了堆结构的性质。这么简单的替换,还是会破坏堆次序。那么第二步,将新节点“下沉”来恢复堆次序。图 4 所示的是一系列交换操作来使新节点“下沉”到正确的位置。

图 4:替换后的根节点下沉

为了保持堆次序,我们需将新的根节点沿着一条路径“下沉”,直到比两个子节点都小。在选择下沉路径时,如果新根节点比子节点大,那么选择较小的子节点与之交换。Listing 4 所示的是新节点下沉所需的percDown和minChild方法的代码。

Listing 4

def percDown(self,i):

while (i * 2) <= self.currentSize:

mc = self.minChild(i)

if self.heapList[i] > self.heapList[mc]:

tmp = self.heapList[i]

self.heapList[i] = self.heapList[mc]

self.heapList[mc] = tmp

i = mc

def minChild(self,i):

if i * 2 + 1 > self.currentSize:

return i * 2

else:

if self.heapList[i*2] < self.heapList[i*2+1]:

return i * 2

else:

return i * 2 + 1

Listing 5 所示的是delMin操作的代码。可以看到比较麻烦的地方由一个辅助函数来处理,即percDown。

Listing 5

def delMin(self):

retval = self.heapList[1]

self.heapList[1] = self.heapList[self.currentSize]

self.currentSize = self.currentSize - 1

self.heapList.pop()

self.percDown(1)

return retval

关于二叉堆的最后一部分便是找到从无序列表生成一个“堆”的方法。我们首先想到的是,将无序列表中的每个元素依次插入到堆中。对于一个排好序的列表,我们可以用二分搜索找到合适的位置,然后在下一个位置插入这个键值到堆中,时间复杂度为O(logn)。另外插入一个元素到列表中需要将列表的一些其他元素移动,为新节点腾出位置,时间复杂度为O(n)。因此用insert方法的总开销是O(nlogn)。其实我们能直接将整个列表生成堆,将总开销控制在O(n)。Listing 6 所示的是生成堆的操作。

Listing 6

def buildHeap(self,alist):

i = len(alist) // 2

self.currentSize = len(alist)

self.heapList = [0] + alist[:]

while (i > 0):

self.percDown(i)

i = i - 1

图 5:将列表[ 9, 6, 5, 2, 3]生成一个二叉堆

图 5 所示的是利用buildHeap方法将最开始的树[ 9, 6, 5, 2, 3]中的节点移动到正确的位置时所做的交换操作。尽管我们从树中间开始,然后回溯到根节点,但percDown方法保证了最大子节点总是“下沉”。因为堆是完全二叉树,任何在中间的节点都是叶节点,因此没有子节点。注意,当i=1时,我们从根节点开始下沉,这就需要进行大量的交换操作。可以看到,图 5 最右边的两颗树,首先 9 从根节点的位置移走,移到下一层级之后,percDown进一步检查它此时的子节点,保证它下降到不能再下降为止,即下降到正确的位置。然后进行第二次交换,9 和 3 的交换。由于 9 已经移到了树最底层的层级,便无法进一步交换了。比较一下列表表示法和图 5 所示的树表示法进行的一系列交换还是很有帮助的。

i = 2 [0, 9, 5, 6, 2, 3]

i = 1 [0, 9, 2, 6, 5, 3]

i = 0 [0, 2, 3, 6, 5, 9]

下列所示的代码是完全二叉堆的实现。

class BinHeap:

def __init__(self):

self.heapList = [0]

self.currentSize = 0

def percUp(self,i):

while i // 2 > 0:

if self.heapList[i] < self.heapList[i // 2]:

tmp = self.heapList[i // 2]

self.heapList[i // 2] = self.heapList[i]

self.heapList[i] = tmp

i = i // 2

def insert(self,k):

self.heapList.append(k)

self.currentSize = self.currentSize + 1

self.percUp(self.currentSize)

def percDown(self,i):

while (i * 2) <= self.currentSize:

mc = self.minChild(i)

if self.heapList[i] > self.heapList[mc]:

tmp = self.heapList[i]

self.heapList[i] = self.heapList[mc]

self.heapList[mc] = tmp

i = mc

def minChild(self,i):

if i * 2 + 1 > self.currentSize:

return i * 2

else:

if self.heapList[i*2] < self.heapList[i*2+1]:

return i * 2

else:

return i * 2 + 1

def delMin(self):

retval = self.heapList[1]

self.heapList[1] = self.heapList[self.currentSize]

self.currentSize = self.currentSize - 1

self.heapList.pop()

self.percDown(1)

return retval

def buildHeap(self,alist):

i = len(alist) // 2

self.currentSize = len(alist)

self.heapList = [0] + alist[:]

while (i > 0):

self.percDown(i)

i = i - 1

bh = BinHeap()

bh.buildHeap([9,5,6,2,3])

print(bh.delMin())

print(bh.delMin())

print(bh.delMin())

print(bh.delMin())

print(bh.delMin())

能在O(n)的开销下能生成二叉堆看起来可能有点不可思议,其证明超出了本书的范围。但是,要理解用O(n)的开销能生成堆的关键是因为logn因子基于树的高度。而对于buildHeap里的许多操作,树的高度比logn要小。

相关阅读:

python优先队列及二叉堆的实现

python数据结构之二叉树的实现

python 二叉树的统计与转换代码

python遍历二叉树的实例

Python实现二叉堆

Python利用前序和中序遍历结果重建二叉树的方法

Python字符串特性及常用字符串方法的简单笔记

Python设计模式中单例模式的实现及在Tornado中的应用

Python优先队列实现方法示例

Python多线程thread与threading的实现方法

python操作mongodb根据_id查询数据的实现方法

python结合shell查询google关键词排名的实现代码

python优先队列的库,python优先队列及二叉堆的实现相关推荐

  1. 结构之美——优先队列基本结构(四)——二叉堆、d堆、左式堆

    实现优先队列结构主要是通过堆完成,主要有:二叉堆.d堆.左式堆.斜堆.二项堆.斐波那契堆.pairing 堆等. 1. 二叉堆 1.1. 定义 完全二叉树,根最小. 存储时使用层序. 1.2. 操作 ...

  2. 图论——Dijkstra+prim算法涉及到的优先队列(二叉堆)

    [0]README 0.1)为什么有这篇文章?因为 Dijkstra算法的优先队列实现 涉及到了一种新的数据结构,即优先队列(二叉堆)的操作需要更改以适应这种新的数据结构,我们暂且吧它定义为Dista ...

  3. 完全二叉树——二叉堆(BinaryHeap)

    前言 优先队列是允许至少下列两种操作的数据结构:insert(插入)以及deleteMin(删除最小者),其中deleteMin的工作是找出.返回.并删除优先队列中最小的元素.insert操作等价于e ...

  4. 优先队列与相关题目(Python、二叉堆)

    1. 优先队列知识 1.1 优先队列简介 优先队列:一种特殊的队列.在优先队列中,元素被赋予优先级,当访问队列元素时,具有最高优先级的元素最先删除. 优先队列与普通队列最大的不同点在于出队顺序 普通队 ...

  5. Python Pillow(PIL)库的用法介绍(二)

    Python Pillow(PIL)库的用法介绍(二) 在上一篇文章中介绍了Pillow库的一些基本用法,参考:https://blog.csdn.net/weixin_43790276/articl ...

  6. python 入门题库————python语句和基础数理

    python 入门题库 python 题库 Python使用符号_______表示注释 Python不支持的数据类型有 查看python版本的命令是 在Python中,print(type(16/4) ...

  7. 二叉堆与自定义优先队列实现删除任意元素

    二叉堆与自定义优先队列实现删除任意元素 堆Heap 二叉堆Binary Heap 二叉堆 二叉堆的实现 插入(insert) 取出堆顶(extract / delete max) 优先队列(Prior ...

  8. 排序算法之——优先队列经典实现(基于二叉堆)

    许多应用都需要处理有序的元素,但有时,我们不要求所有元素都有序,或是一定要一次就将它们排序,许多情况下,我们会收集这些元素里的最大值或最小值. 这种情况下一个合适的数据结构应该支持两种操作:插入元素. ...

  9. python实现二叉堆中的大顶堆(大根堆)

    堆(英语:heap)是计算机科学中一类特殊的数据结构的统称.堆通常是一个可以被看做一棵树的数组对象.堆总是满足下列性质: 堆中某个节点的值总是不大于或不小于其父节点的值: 堆总是一棵完全二叉树. 将根 ...

最新文章

  1. Team Foundation Server安装指南(转)
  2. linux 同步与异步--阻塞与非阻塞型I/O
  3. 解决 VCENTER ROOT 密码过期无法登陆 USER PASSWORD EXPIRED
  4. OSX unable to write 'random state'
  5. List与Set接口的方法
  6. jqgrid表格下拉搜索多选框优化—使用select下拉多选插件
  7. 通用职责分配软件原则之8-中介原则
  8. SAP License:SAP评论
  9. Tuxera NTFS有什么优点可以让我们使用?
  10. 没解决:eclipse的JDK Compliance的Use compliance from execution无法选中
  11. qq令牌64位密钥提取_qq令牌绑定工具无需密码2020
  12. Python 构建 Random Forest 和 XGBoost
  13. 100条超实用微信营销技巧:公众号、朋友圈和微信营销
  14. BEV蒸馏来了!BEVDistill:用于多目3D目标检测的跨模态BEV蒸馏
  15. 见证云力量|飞马网技术沙龙“云计算专场”圆满结束
  16. 面试题6:从尾到头打印链表
  17. HIT 软件构造 lab2
  18. OpenCV边缘检测(一)——Roberts边缘检测
  19. 小米十周年雷军演讲全文:和这个伟大时代同行,是最大的荣幸
  20. 北京电子科技学院计算机考研资料汇总

热门文章

  1. [转]项目管理有感之一 沟通
  2. Lync Server 2010标准版系列PART3:证书准备
  3. BERT: Bidirectional Encoder Representations from Transformers双向Transformer用于语言模型 NAACL 2018
  4. yum方式安装mysql多实例_centos7下使用yum安装mysql
  5. 从0到1上线一个微信小程序
  6. 深信服云计算BU专家组组长王佳玮:全融合时代的企业云新架构
  7. 不同浏览器对回车提交表单的处理办法(转载)
  8. 公共技术点之 Java 注解 Annotation
  9. VMware vCenter Server安装
  10. 快速锁定Windows XP桌面的几个方法