Python中的heapq模块源码详析


Posted in Python onJanuary 08, 2019

起步

这是一个相当实用的内置模块,但是很多人竟然不知道他的存在——笔者也是今天偶然看到的,哎……尽管如此,还是改变不了这个模块好用的事实

heapq 模块实现了适用于Python列表的最小堆排序算法。

Python中的heapq模块源码详析

堆是一个树状的数据结构,其中的子节点都与父母排序顺序关系。因为堆排序中的树是满二叉树,因此可以用列表来表示树的结构,使得元素 N 的子元素位于 2N + 1 和 2N + 2 的位置(对于从零开始的索引)。

本文内容将分为三个部分,第一个部分简单介绍 heapq 模块的使用;第二部分回顾堆排序算法;第三部分分析heapq中的实现。

heapq 的使用

创建堆有两个基本的方法:heappush() 和 heapify(),取出堆顶元素用 heappop()。

heappush() 是用来向已有的堆中添加元素,一般从空列表开始构建:

import heapq

data = [97, 38, 27, 50, 76, 65, 49, 13]
heap = []

for n in data:
 heapq.heappush(heap, n)

print('pop:', heapq.heappop(heap)) # pop: 13
print(heap) # [27, 50, 38, 97, 76, 65, 49]

如果数据已经在列表中,则使用 heapify() 进行重排:

import heapq

data = [97, 38, 27, 50, 76, 65, 49, 13]

heapq.heapify(data)

print('pop:', heapq.heappop(data)) # pop: 13
print(data) # [27, 38, 49, 50, 76, 65, 97]

回顾堆排序算法

堆排序算法基本思想是:将无序序列建成一个堆,得到关键字最小(或最大的记录;输出堆顶的最小 (大)值后,使剩余的 n-1 个元素 重又建成一个堆,则可得到n个元素的次小值 ;重复执行,得到一个有序序列,这个就是堆排序的过程。

堆排序需要解决两个问题:

  • 如何由一个无序序列建立成一个堆?
  • 如何在输出堆顶元素之后,调整剩余元素,使之成为一个新的堆?
  • 新添加元素和,如何调整堆?

先来看看第二个问题的解决方法。采用的方法叫“筛选”,当输出堆顶元素之后,就将堆中最后一个元素代替之;然后将根结点值与左、右子树的根结点值进行比较 ,并与其中小者进行交换;重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”。

Python中的heapq模块源码详析

如上图所示,当堆顶 13 输出后,将堆中末尾的 97 替代为堆顶,然后堆顶与它的子节点 38 和 27 中的小者交换;元素 97 在新的位置上在和它的子节点 65 和 49 中的小者交换;直到元素97成为叶节点,就得到了新的堆。这个过程也叫 下沉 。

让堆中位置为 pos 元素进行下沉的如下:

def heapdown(heap, pos):
 endpos = len(heap)
 while pos < endpos:
 lchild = 2 * pos + 1
 rchild = 2 * pos + 2
 if lchild >= endpos: # 如果pos已经是叶节点,退出循环
  break
 childpos = lchild # 假设要交换的节点是左节点
 if rchild < endpos and heap[childpos] > heap[rchild]:
  childpos = rchild

 if heap[pos] < heap[childpos]: # 如果节点比子节点都小,退出循环
  break
 heap[pos], heap[childpos] = heap[childpos], heap[pos] # 交换
 pos = childpos

再来看看如何解决第三个问题:新添加元素和,如何调整堆?这个的方法正好与 下沉 相反,首先将新元素放置列表的最后,然后新元素与其父节点比较,若比父节点小,与父节点交换;重复过程直到比父节点大或到根节点。这个过程使得元素从底部不断上升,从下至上恢复堆的顺序,称为 上浮 。

将位置为 pos 进行上浮的代码为:

def heapup(heap, startpos, pos): # 如果是新增元素,startpos 传入 0
 while pos > startpos:
 parentpos = (pos - 1) // 2
 if heap[pos] < heap[parentpos]:
  heap[pos], heap[parentpos] = heap[parentpos], heap[pos]
  pos = parentpos
 else:
  break

第一个问题:如何由一个无序序列建立成一个堆?从无序序列的第 n/2 个元素 (即此无序序列对应的完全二叉树的最后一个非终端结点 )起 ,至第一个元素止,依次进行下沉:

Python中的heapq模块源码详析

for i in reversed(range(len(data) // 2)):
 heapdown(data, i)

heapq 源码分析

添加新元素到堆中的 heappush() 函数:

def heappush(heap, item):
 """Push item onto heap, maintaining the heap invariant."""
 heap.append(item)
 _siftdown(heap, 0, len(heap)-1)

把目标元素放置列表最后,然后进行上浮。尽管它命名叫 down ,但这个过程是上浮的过程,这个命名也让我困惑,后来我才知道它是因为元素的索引不断减小,所以命名 down 。下沉的过程它也就命名为 up 了。

def _siftdown(heap, startpos, pos):
 newitem = heap[pos]
 # Follow the path to the root, moving parents down until finding a place
 # newitem fits.
 while pos > startpos:
  parentpos = (pos - 1) >> 1
  parent = heap[parentpos]
  if newitem < parent:
   heap[pos] = parent
   pos = parentpos
   continue
  break
 heap[pos] = newitem

一样是通过 newitem 不断与父节点比较。不一样的是这里缺少了元素交换的过程,而是计算出新元素最后所在的位置 pos 并进行的赋值。显然这是优化后的代码,减少了不断交换元素的冗余过程。

再来看看输出堆顶元素的函数 heappop():

def heappop(heap):
 """Pop the smallest item off the heap, maintaining the heap invariant."""
 lastelt = heap.pop() # raises appropriate IndexError if heap is empty
 if heap:
  returnitem = heap[0]
  heap[0] = lastelt
  _siftup(heap, 0)
  return returnitem
 return lastelt

通过 heap.pop() 获得列表中的最后一个元素,然后替换为堆顶 heap[0] = lastelt ,再进行下沉:

def _siftup(heap, pos):
 endpos = len(heap)
 startpos = pos
 newitem = heap[pos]
 # Bubble up the smaller child until hitting a leaf.
 childpos = 2*pos + 1 # 左节点,默认替换左节点
 while childpos < endpos:
  # Set childpos to index of smaller child.
  rightpos = childpos + 1 # 右节点
  if rightpos < endpos and not heap[childpos] < heap[rightpos]:
   childpos = rightpos # 当右节点比较小时,应交换的是右节点
  # Move the smaller child up.
  heap[pos] = heap[childpos]
  pos = childpos
  childpos = 2*pos + 1
 # The leaf at pos is empty now. Put newitem there, and bubble it up
 # to its final resting place (by sifting its parents down).
 heap[pos] = newitem
 _siftdown(heap, startpos, pos)

这边的代码将准备要下沉的元素视为新元素 newitem ,将其当前的位置 pos 视为空位置,由其子节点中的小者进行取代,反复如此,最后会在叶节点留出一个位置,这个位置放入 newitem ,再让新元素进行上浮。

再来看看让无序数列重排成堆的 heapify() 函数:

def heapify(x):
 """Transform list into a heap, in-place, in O(len(x)) time."""
 n = len(x)
 for i in reversed(range(n//2)):
  _siftup(x, i)

这部分就和理论上的一致,从最后一个非叶节点 (n // 2) 到根节点为止,进行下沉。

总结

堆排序结合图来理解还是比较好理解的。这种数据结构常用于优先队列(标准库Queue的优先队列用的就是堆)。 heapq 模块中还有很多其他 heapreplace ,heappushpop 等大体上都很类似。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
跟老齐学Python之不要红头文件(1)
Sep 28 Python
numpy.random.seed()的使用实例解析
Feb 03 Python
Python实现计算文件MD5和SHA1的方法示例
Jun 11 Python
python 批量修改 labelImg 生成的xml文件的方法
Sep 09 Python
基于Python新建用户并产生随机密码过程解析
Oct 08 Python
Python中sorted()排序与字母大小写的问题
Jan 14 Python
使用python采集Excel表中某一格数据
May 14 Python
Python轻量级web框架bottle使用方法解析
Jun 13 Python
基于Python+QT的gui程序开发实现
Jul 03 Python
详解Python+Selenium+ChromeDriver的配置和问题解决
Jan 19 Python
python 30行代码实现蚂蚁森林自动偷能量
Feb 08 Python
Django debug为True时,css加载失败的解决方案
Apr 24 Python
python使用PIL模块获取图片像素点的方法
Jan 08 #Python
python 获取图片分辨率的方法
Jan 08 #Python
Python 处理图片像素点的实例
Jan 08 #Python
Python实现查找最小的k个数示例【两种解法】
Jan 08 #Python
对Python闭包与延迟绑定的方法详解
Jan 07 #Python
python将控制台输出保存至文件的方法
Jan 07 #Python
对Python捕获控制台输出流的方法详解
Jan 07 #Python
You might like
PHP生成带有雪花背景的验证码
2008/09/28 PHP
php面向对象全攻略 (十四) php5接口技术
2009/09/30 PHP
将PHP程序中返回的JSON格式数据用gzip压缩输出的方法
2016/03/03 PHP
php-fpm中max_children的配置
2019/03/15 PHP
优化 JavaScript 代码的方法小结
2009/07/16 Javascript
ajax中get和post的说明及使用与区别
2012/12/23 Javascript
一个背景云变换js特效 鼠标移动背景云变化
2012/12/28 Javascript
jQuery 遍历map()方法详解
2016/11/04 Javascript
原生js实现日期计算器功能
2017/02/17 Javascript
vue中路由参数传递可能会遇到的坑
2017/12/07 Javascript
vue中element 上传功能的实现思路
2018/07/06 Javascript
TypeScript 运行时类型检查补充工具
2020/09/28 Javascript
[02:43]DOTA2英雄基础教程 半人马战行者
2014/01/13 DOTA
python抓取网页中图片并保存到本地
2015/12/01 Python
Python使用正则表达式抓取网页图片的方法示例
2017/04/21 Python
django基础之数据库操作方法(详解)
2017/05/24 Python
Python实现的微信公众号群发图片与文本消息功能实例详解
2017/06/30 Python
详解Python并发编程之从性能角度来初探并发编程
2019/08/23 Python
python生成器用法实例详解
2019/11/22 Python
python实现while循环打印星星的四种形状
2019/11/23 Python
pandas中read_csv的缺失值处理方式
2019/12/19 Python
Django框架请求生命周期实现原理
2020/11/13 Python
python批量生成身份证号到Excel的两种方法实例
2021/01/14 Python
美国大型的健身社区和补充商店:Bodybuilding.com
2016/09/06 全球购物
化石印度尼西亚在线商店:Fossil Indonesia
2019/03/11 全球购物
个性发展自我评价
2014/02/11 职场文书
企业道德讲堂实施方案
2014/03/19 职场文书
公务员更新知识培训实施方案
2014/03/31 职场文书
仓库规划计划书
2014/04/28 职场文书
感恩之星事迹材料
2014/05/03 职场文书
林肯就职演讲稿
2014/05/19 职场文书
多媒体编辑专业毕业生求职信
2014/06/13 职场文书
幼师中班个人总结
2015/02/12 职场文书
行政人事主管岗位职责
2015/04/11 职场文书
接收函
2019/04/22 职场文书
详解MySQL连接挂死的原因
2021/05/18 MySQL