堆(HEAP) 介绍
堆(HEAP)定义: 可以迅速的找到一堆数中的 最大数和最小数
分类:
- 大顶堆(Max Heap)
-
- 最大的元素在堆顶 元素越大优先级越高
- 父节点要比左右孩子大
-
- 小顶堆 (Mix Heap):
- 最小的元素在堆顶 元素越小优先级越高
- 父节点要比左右孩子小
- 常见的有
- 二叉堆
- 斐波那契堆
堆的时间复杂度
- find max O(1) --大顶堆
- delete max O(logN)
- insert O(logN)/O(1)-斐波那契堆
各种堆的时间复杂度对比
二叉堆 介绍
- 通过完全二叉树实现的(属性 叶子是满的 除了最下面的一层)
- 树中 任意根节点的值 >= 子节点的值(所 以根节点是最大的)
- (数组下标)索引为i的左孩子的索引为 (2*i + 1)
- (数组下标)索引为i的右孩子的索引为 (2*i + 2)
- (数组下标)索引为i的父节点的索引是 floor((i-1)/2) 取整
二叉堆的插入操作
- step1 新元素一律插入堆的尾部
- step2 重新维护堆的元素排序 HeapifyUp
- 依次向上调整整个堆的结构(heapifyup)
- 如果 当前元素 > 父节点 则进行交换, 直到 <= 父节点
最坏的时间复杂度 O(logN)
第一步 将插入的元素放入堆的尾部
这个时候 堆的属性被破坏了
第二步 依次和父节点进行比较
直到<= 父节点
大顶堆的插入代码示例
def swap(self, fpos, spos):
"""
交换两个下标的值
:param fpos:
:param spos:
:return:
"""
self.Heap[fpos], self.Heap[spos] = self.Heap[spos], self.Heap[fpos]
def insert_maxheap(self, element):
"""
大顶堆的插入操作
思路
- 新元素插入堆的尾部
- 依次和父节点进行比较
- 如果新元素 > 父节点 则进行交换;否则结束比较 完成插入操作
:param element:
:return:
"""
# 判断是否存在溢出
if self.size >= self.capacity:
return
# 堆的尾部插入新元素
self.size += 1
self.Heap[self.size] = element
# 新元素的下标
current = self.size
# 新元素 > 父节点 则进行交换
while self.Heap[current] > self.Heap[self.parent(current)]:
self.swap(current, self.parent(current))
# 插入元素 获取新的下标(即父节点的下标)
current = self.parent(current)
小顶堆的插入代码示例
def insert(self, element):
"""
小顶堆的插入操作
思路
- 新元素插入堆的尾部
- 依次和父节点进行比较
- 如果新元素 < 父节点 则进行交换;否则结束比较 完成插入操作
:param element:
:return:
"""
# 判断是否存在溢出
if self.size >= self.capacity:
return
# 堆的尾部插入新元素
self.size += 1
self.Heap[self.size] = element
# 新元素的下标
current = self.size
# 新元素 < 父节点 则进行交换
while self.Heap[current] < self.Heap[self.parent(current)]:
self.swap(current, self.parent(current))
# 插入元素 获取新的下标(即父节点的下标)
current = self.parent(current)
2.删除max操作
- 将堆尾的元素 去替代 堆顶的元素
- 依次从根部 向下调整整个堆的结构(一直调整到堆尾部)
- 如果当前结点 < 子节点 则进行交换,
- heapifydown
- 最坏的时间复杂度 O(logN)
删除max示例
最开始的结构
将堆顶元素删除掉
用尾部元素替换 被删除的堆顶元素
和子结点进行比较 和最强的子节点 进行替换 40<-->85
和子结点进行比较 和最强的子节点 进行替换 40<-->8540<-->60
大顶堆删除代码示例
def delete(self):
# 删除第一个下标(即删除最小的元素)
popped = self.Heap[self.FRONT]
# 将堆尾的元素 放入堆顶
self.Heap[self.FRONT] = self.Heap[self.size]
self.size -= 1
# 从下标1 也就是堆顶依次进行对比操作
self.heapifyDown(self.FRONT)
return popped
def heapifyDown_maxheap(self, pos):
"""
大顶堆的操作
- 依次和自己的儿子节点进行比较
- 如果父节点 < 最大的儿子 则将最差的儿子节点和自己交换
- 直到父节点 >= 最大的儿子
:param pos:
:return:
"""
if not self.isLeaf(pos):
if (self.Heap[pos] < self.Heap[self.leftChild(pos)] or
self.Heap[pos] < self.Heap[self.rightChild(pos)]):
# 左节点最大 则左节点和父节点进行交换
if self.Heap[self.leftChild(pos)] > self.Heap[self.rightChild(pos)]:
self.swap(pos, self.leftChild(pos))
self.heapifyDown(self.leftChild(pos))
# 右节点最大 则右节点和父节点进行交换
else:
self.swap(pos, self.rightChild(pos))
self.heapifyDown(self.rightChild(pos))
小顶堆删除代码示例
def heapifyDown(self, pos):
"""
小顶堆的删除操作
思路:
- 将堆顶元素(min values)删除
- 将堆尾元素 替换到堆顶
- 依次和自己的儿子节点进行比较
- 如果父节点 > 最小的儿子 则将最差的儿子节点和自己交换
- 直到父节点 <= 最小的儿子
:param pos:
:return:
"""
if not self.isLeaf(pos):
if (self.Heap[pos] > self.Heap[self.leftChild(pos)] or
self.Heap[pos] > self.Heap[self.rightChild(pos)]):
# 左节点最小 则左节点和父节点进行交换
if self.Heap[self.leftChild(pos)] < self.Heap[self.rightChild(pos)]:
self.swap(pos, self.leftChild(pos))
self.heapifyDown(self.leftChild(pos))
# 右节点最小 则右节点和父节点进行交换
else:
self.swap(pos, self.rightChild(pos))
self.heapifyDown(self.rightChild(pos))
注意
- 二叉堆只是堆的一种实现方式 但不是最优的
- 工程使用的时候 直接调用优先队列(priority queue)