shan

利用二叉堆实现优先级队列

2020-06-06

本文为 python数据结构与算法的读书笔记,主要内容是二叉堆及用二叉堆实现优先级队列实现方式。

队列有个重要的变体,叫做优先级队列,和队列一样,优先级队列也是从头部移出元素,不过元素的逻辑顺序由优先级决定的。优先级高的元素在前面,低的在后面。因此一个元素入队时,他可能直接被移到优先级队列的头部。我们用经典的二叉堆来完成这种数据结构。

二叉堆画出来像是一棵树,但是其在python中实现只需要一个列表就可以完成,它有两个常见的变体:

  • 最小堆,最小的元素一直在队首
  • 最大堆,最大的元素一直在队首

二叉堆的操作

其基本操作如下:

  • BinaryHeap() 新建空的二叉堆
  • insert(k) 往堆中加入一个新元素
  • find_min() 返回最小的元素,元素留在堆中
  • del_min() 返回最小的元素,并将该元素从堆中移除
  • is_empty() 在堆为空时返回True,否则返回False
  • size() 返回堆中元素个数
  • build_heap(list) 根据列表创建堆

二叉堆的实现

为了保证二叉堆对数性能,我们创建了一棵完全二叉树来维持树的平衡,从而达到要求的性能。在完全二叉树中,除了最底层,其他每一层的节点都是满的,在最底层,从左往右填充节点。如下图:

完全二叉树可以用列表来表示,而不是常用表示树的方法,如列表之列表或者节点与引用等方法表示。由于树时完全的,因此对于在列表中处于位置p的节点来说,其左子节点正好处于位置2p;右子节点处于位置2p+1。若要找到树中任意节点的父节点,只需要执行整数除法即可。

我们用来存储堆元素的方法依赖于堆的有序性,其定义为:对于堆中任意元素x及其父元素p,p都不打于x。

我们想要实现二叉堆,其主要步骤如下

  • 那么首先需要是吸纳二叉堆的构造方法。首先初始化这个列表及其当前大小。
  • 下一步即使实现insert() 方法,将元素加入列表最简单高效的方式就是将元素追加到列表末尾,然后与其父节点及兄弟节点进行比较交换操作,从而维护堆的结构性质。在实现此方法之前,需要先实现向上比较交换方法perc_up()
  • 定义完成insert()方法之后,需要编写del_min() 方法。其查找简单,难点在于如何在移出根节点之后重新获取堆的结构性质和有序性。可以分两步完成:第一步,取出列表中最后一个元素,将其移到根节点的位置,这步操作保留了堆的性质,但破坏了二叉堆的有序性。第二步,将新的根节点沿着树推到正确的位置,以重获堆的有序性。在这步之前同样需要实现向下比较交换的方法 perc_down() 和查找最小子节点的方法min_child()

其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# -*- coding: utf-8 -*-
"""
@file: binary_heap.py
@author: syb
@time: 2020/06/03
"""


class BinaryHeap:
def __init__(self):
self.heap_list = [0] # 将列表第0个元素填充,这样下面的操作更加方便
self.current_size = 0

def perc_up(self, pos):
"""
向上比较并交换
:param pos:
:return:
"""
while pos // 2 > 0:
if self.heap_list[pos] < self.heap_list[pos // 2]:
self.heap_list[pos], self.heap_list[pos // 2] = self.heap_list[pos // 2], self.heap_list[pos]

pos //= 2

def perc_down(self, pos):
"""
向下比较并交换
:param pos:
:return:
"""
while pos * 2 <= self.current_size:
mc_pos = self.min_child(pos) # 获取最小子节点位置
if self.heap_list[pos] > self.heap_list[mc_pos]:
self.heap_list[pos], self.heap_list[mc_pos] = self.heap_list[mc_pos], self.heap_list[pos]

pos = mc_pos

def min_child(self, pos):
"""
获取最小子节点位置
:param pos:
:return:
"""
if pos * 2 + 1 > self.current_size:
return pos * 2
else:
if self.heap_list[pos * 2] < self.heap_list[pos * 2 + 1]:
return pos * 2
else:
return pos * 2 + 1

def insert(self, node):
"""
插入元素
:param node:
:return:
"""
self.heap_list.append(node)
self.current_size += 1
self.perc_up(self.current_size)

def del_min(self):
"""
删除最小元素
:return:
"""
min_child = self.heap_list[1]
self.heap_list[1] = self.heap_list[self.current_size]
self.current_size -= 1
self.heap_list.pop()
self.perc_down(1)

return min_child

def is_empty(self):
"""
查看堆是否为空
:return:
"""
return False if self.current_size else True

def size(self):
"""
返回堆的大小
:return:
"""
return self.current_size

def build_heap(self, alist):
"""
从列表新建堆
"""
i = len(alist) // 2
self.current_size = len(alist)
self.heap_list = [0] + [:]
while i > 0:
self.percdown(i)
i -= 1
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章