<Python 算法与数据结构视频教程> 学习笔记
1.什么是堆
数据结构-树介绍了什么是树,以及二叉树的实现。还记得树的三种特殊结构吗?完美二叉树,满二叉树和完全二叉树。这里介绍的堆结构就是一种完全二叉树。堆可分为最大堆和最小堆,区别就是父节点是否大于所有子节点。最大堆的父节点大于它的子节点,而最小堆中子节点大于父节点。看图有个清晰的认识:

2. 堆的表示
堆可以使用list实现,就是按照层序遍历顺序将每个节点上的值存放在数组中。父节点和子节点之间存在如下的关系:
parent = int((i-1) / 2) # 取整
left = 2 * i + 1
right = 2 * i + 2
其中i表示数组中的索引,如果left、right的值超出了数组的索引,则表示这个节点是不存在的。

3.堆的操作
(1)往堆中插入值,sift-up操作:

往最大堆里添加一个元素,我们在使用数组实现的时候直接使用append()方法将值添加到数组的最后。这时候我们需要维持最大堆的特性,如下图。添加的新值90首先被放到堆的最后,然后与父节点的值作比较,如果比父节点值大,则交换位置。
这里涉及到的问题是子节点与父节点之间的关系。
# 堆中父节点i与子节点left、right的位置关系
parent = int((i-1) / 2) # 取整
left = 2 * i + 1
right = 2 * i + 2
# 已知子节点的位置j,求父节点的位置
parent = int((j-1)/2)
使用递归的方式,向上比较,直到根节点。
(2)获取或删除根节点,sift-down操作;

当我们把最大或者最小的值从堆中弹出,为了维持堆的特性,要使用sift-down操作。因为最大堆、最小堆的最值都在根节点,当弹出并返回根节点的值后,为了维持堆的特性,我们先将最后一个位置上的值放到根节点中。然后比较它与它的两个子节点中三个值的大小,选择最大的值放到父节点上。同理,我们这里也是使用递归的方式向下比较。这里涉及到两个问题:
根据父节点确定子节点的位置:
# 父节点的位置,left,rigth为左右子节点的位置
left = 2 * ndx + 1
right = 2 * ndx + 2
交换位置要满足几个条件条件,比如跟左子节点交换的条件:
- 存在左子节点,
- 左子节点大于右子节点,
- 左子节点大于父节点
4. 堆的实现
使用Python实现堆结构,这里实现的是最大堆,里面用到了自己实现的数组,把它理解为list就行。
因为在看一个视频教程,复制粘贴了一下老师的代码【这里】。
最大堆
class Array(object):
"""
Achieve an Array by Python list
"""
def __init__(self, size = 32):
self._size = size
self._items = [None] * size
def __getitem__(self, index):
"""
Get items
:param index: get a value by index
:return: value
"""
return self._items[index]
def __setitem__(self, index, value):
"""
set item
:param index: giving a index you want to teset
:param value: the value you want to set
:return:
"""
self._items[index] = value
def __len__(self):
"""
:return: the length of array
"""
return self._size
def clear(self, value=None):
"""
clear the Array
:param value: set all value to None
:return: None
"""
for i in range(self._size):
self._items[i] = value
def __iter__(self):
for item in self._items:
yield item
class MaxHeap(object):
def __init__(self, maxsize=None):
self.maxsize = maxsize
self._elements = Array(maxsize)
self._count = 0
def __len__(self):
return self._count
def add(self, value):
if self._count >= self.maxsize:
raise Exception('full')
self._elements[self._count] = value
self._count += 1
self._siftup(self._count-1) # 维持堆的特性
def _siftup(self, ndx):
if ndx > 0:
parent = int((ndx-1)/2)
if self._elements[ndx] > self._elements[parent]: # 如果插入的值大于 parent,一直交换
self._elements[ndx], self._elements[parent] = self._elements[parent], self._elements[ndx]
self._siftup(parent) # 递归
def extract(self):
if self._count <= 0:
raise Exception('empty')
value = self._elements[0] # 保存 root 值
self._count -= 1
self._elements[0] = self._elements[self._count] # 最右下的节点放到root后siftDown
self._siftdown(0) # 维持堆特性
return value
def _siftdown(self, ndx):
left = 2 * ndx + 1
right = 2 * ndx + 2
# determine which node contains the larger value
largest = ndx
if (left < self._count and # 有左孩子
self._elements[left] >= self._elements[largest] and
self._elements[left] >= self._elements[right]): # 原书这个地方没写实际上找的未必是largest
largest = left
elif right < self._count and self._elements[right] >= self._elements[largest]:
largest = right
if largest != ndx:
self._elements[ndx], self._elements[largest] = self._elements[largest], self._elements[ndx]
self._siftdown(largest)
def test_maxheap():
import random
n = 5
h = MaxHeap(n)
for i in range(n):
h.add(i)
for i in reversed(range(n)):
assert i == h.extract()
最小堆
class Array(object):
"""
Achieve an Array by Python list
"""
def __init__(self, size = 32):
self._size = size
self._items = [None] * size
def __getitem__(self, index):
"""
Get items
:param index: get a value by index
:return: value
"""
return self._items[index]
def __setitem__(self, index, value):
"""
set item
:param index: giving a index you want to teset
:param value: the value you want to set
:return:
"""
self._items[index] = value
def __len__(self):
"""
:return: the length of array
"""
return self._size
def clear(self, value=None):
"""
clear the Array
:param value: set all value to None
:return: None
"""
for i in range(self._size):
self._items[i] = value
def __iter__(self):
for item in self._items:
yield item
class MinHeap(object):
"""
Achieve a minimum heap by Array
"""
def __init__(self, maxsize = None):
self.maxsize = maxsize
self._elements = Array(maxsize)
self._count = 0
def __len__(self):
return self._count
def add(self, value):
"""
Add an element to heap while keeping the attribute of heap unchanged.
:param value: the value added to the heap
:return: None
"""
if self._count >= self.maxsize:
raise Exception("The heap is full!")
self._elements[self._count] = value
self._count += 1
self._siftup(self._count-1)
def _siftup(self, index):
"""
To keep the the attribute of heap unchanged while adding a new value.
:param index: the index of value you want to swap
:return: None
"""
if index > 0:
parent = int((index - 1) / 2)
if self._elements[parent] > self._elements[index]:
self._elements[parent], self._elements[index] = self._elements[index], self._elements[parent]
self._siftup(parent)
def extract(self):
"""
pop and return the value of root
:return: the value of root
"""
if self._count <= 0:
raise Exception('The heap is empty!')
value = self._elements[0]
self._count -= 1
self._elements[0] = self._elements[self._count]
self._siftdown(0)
return value
def _siftdown(self, index):
"""
to keep the attribute of heap unchanged while pop out the root node.
:param index: the index of value you want to swap
:return: None
"""
if index < self._count:
left = 2 * index + 1
right = 2 * index + 2
if left < self._count and right < self._count \
and self._elements[left] <= self._elements[right] \
and self._elements[left] <= self._elements[index]:
self._elements[left], self._elements[index] = self._elements[index], self._elements[left]
self._siftdown(left)
elif left < self._count and right < self._count \
and self._elements[left] >= self._elements[right] \
and self._elements[right] <= self._elements[index]:
self._elements[right], self._elements[index] = self._elements[index], self._elements[right]
self._siftdown(left)
if left < self._count and right > self._count \
and self._elements[left] <= self._elements[index]:
self._elements[left], self._elements[index] = self._elements[index], self._elements[left]
self._siftdown(left)
if __name__ == '__main__':
import random
n = 5
h = MinHeap(n)
for i in range(n):
h.add(i)
for i in range(n):
assert i == h.extract()
5. Python中的heapq模块
这个模块提供了的堆是一个最小堆,索引值从0开始。而很多教材中都使用最大堆作为教学的例子,因为其排序是稳定的,而最小堆排序是不稳定的。
Python中创建一个堆可以直接使用list的创建方式H = [], 或者使用heapify()函数将一个存在的列表转为堆。
这个模块提供了下面几种堆的操作:
heapq.heappush(heap, item)
往堆中插入一个值,同时要保持为最小堆。
heapq.heappop(heap)
返回堆中的最小值,并把它从堆中删除,同时保持为最小堆;如果堆为空,发生 IndexError。直接通过heap[0]可以获取最小值并不从堆中把它删除。
heapq.heappushpop(heap, item)
向堆中插入值后再弹出堆中的最小值,这个函数的速度比直接使用heappush() 和heappop()的效率更加高。
heapq.heapreplace(heap, item)
弹出和返回堆中的最小值再插入一个新的值。堆的大小没有改变。如果堆为空,产生 IndexError。
这一个操作也比直接使用heappush() 和heappop()的效率更加高,尤其适合使用在固定堆大小不变的情况。
与上一个函数相比,这个函数返回的值可能要比将要插入到堆的值大。
heapq.heapify(x)
将一个list转为最小堆,线性时间复杂度,O(n).
6.堆排序
堆已经实现了,下面可以实现堆排序。