1、什么是二叉堆html
1. 堆的定义:python
堆(heap),这里指的堆是一种数据结构,不是内存模型中的堆。堆一般能够看做为一棵树,但这棵树得知足如下条件:算法
a. 堆中任意节点的值老是不大于(不小于)其子节点的值;api
b. 堆老是一颗彻底树。数组
将任意节点不大于其子节点的堆叫作最小堆或小根堆,而将任意节点不小于其子节点的堆叫作最大堆或者大根堆。常见的堆有二叉堆,左倾堆,斜堆,二项堆,斐波那契堆等等。数据结构
2. 二叉堆:app
二叉堆是彻底二叉树,它分为两种:最大堆和最小堆。
spa
最大堆:父结点的键值老是大于或等于任何一个子节点的键值;最小堆:父结点的键值老是小于或等于任何一个子节点的键值。示意图以下:操作系统
2、二叉堆的存储code
二叉堆是一颗二叉树,所以咱们很容易想到使用链式存储,可是二叉堆是一颗彻底二叉树,所以咱们可使用数组这种更简单高效的存储方式。
咱们将二叉堆的第一个元素放在数组索引的0的位置,也能够放在索引为1的位置。固然,它们的本质是同样的。
当第一个元素放在索引为0的位置上时:
1. 索引为 i 的左孩子的索引为(2*i + 1)
2. 索引为 i 的右孩子的索引为 (2*i + 2)
3. 索引为 i 的父节点的索引为 (i - 1)/ 2(计算机里取整)
二叉堆及其数组存储方式以下:
当第一个元素放在索引为1的位置上时:
1. 索引为 i 的左孩子的索引为(2*i )
2. 索引为 i 的右孩子的索引为 (2*i + 1)
3. 索引为 i 的父节点的索引为 (i )/ 2(计算机里取整)
二叉堆及其数组存储方式以下:
3、二叉堆的基本操做:shift_up与shift_down
咱们以最大堆来演示二叉堆的插入与删除对应的shift_up与shift_down操做
1. 插入数据---shift_up:
例如,在最大堆[90,80,70,60,40,30,20,10,50]中添加85,须要执行的步骤以下:
插入数据基本过程以下:
a. 将数据加入到最大堆的末尾,即数组最后
b. 而后经过shift_up操做把数据尽量的往上挪,直到挪不动为止
所以,插入的最关键步骤为shift_up,最大堆插入的代码以下:
class MaxHeap: heap = [] @staticmethod def insert(num): MaxHeap.heap.append(num) MaxHeap.shift_up() @staticmethod def shift_up(): current_id = len(MaxHeap.heap) - 1 parent_id = (current_id - 1)//2 while current_id > 0: if MaxHeap.heap[parent_id] >= MaxHeap.heap[current_id]: break else: MaxHeap.heap[parent_id], MaxHeap.heap[current_id] = MaxHeap.heap[current_id], MaxHeap.heap[parent_id] current_id = parent_id parent_id = (current_id -1)//2
2. 删除数据---shift_down:
如例,从最大堆[90,85,70,60,80,30,20,10,50,40]中删除90,须要执行的步骤以下:
删除数据的步骤以下:
a. 删除该数据m,但数组结构不变,即其余数据位置不发生移动
b. 将数组最后一个数据n移动到刚才删除的数据m的索引处
c. 经过shift_down操做,把数据n,尽可能往下挪,直到生于的数组从新成为最大堆
所以,删除的最关键步骤为shift_down,最大堆删除的代码以下:
class MaxHeap: heap = [90,85,70,60,80,30,20,10,50,40] @staticmethod def insert(num): MaxHeap.heap.append(num) MaxHeap.shift_up() @staticmethod def shift_up(): current_id = len(MaxHeap.heap) - 1 parent_id = (current_id - 1)//2 while current_id > 0: if MaxHeap.heap[parent_id] >= MaxHeap.heap[current_id]: break else: MaxHeap.heap[parent_id], MaxHeap.heap[current_id] = MaxHeap.heap[current_id], MaxHeap.heap[parent_id] current_id = parent_id parent_id = (current_id -1)//2 @staticmethod def delate(num): temp = MaxHeap.heap.pop() ind = MaxHeap.heap.index(num) MaxHeap.heap[ind] = temp MaxHeap.shift_down(ind) @staticmethod def shift_down(ind): current_id = ind child_id_left = current_id * 2 + 1 child_id_right = current_id * 2 + 2 while current_id < len(MaxHeap.heap) - 1: #若是当前节点为叶子节点,shift_down完成 if current_id * 2 + 1 > len(MaxHeap.heap) - 1: break #若是当前节点只有左孩子没有右孩子 if current_id * 2 + 1 == len(MaxHeap.heap) - 1: if MaxHeap.heap[current_id] > MaxHeap.heap[-1]: break else: MaxHeap.heap[current_id], MaxHeap.heap[-1] = MaxHeap.heap[-1], MaxHeap.heap[current_id] break #若是当前节点既有左孩子又有右孩子 if MaxHeap.heap[current_id] > max(MaxHeap.heap[child_id_left], MaxHeap.heap[child_id_right]): break else: if MaxHeap.heap[child_id_right] > MaxHeap.heap[child_id_left]: MaxHeap.heap[child_id_right], MaxHeap.heap[current_id] = MaxHeap.heap[current_id], MaxHeap.heap[child_id_right] current_id = child_id_right child_id_left = current_id * 2 + 1 child_id_right = current_id * 2 + 2 else: MaxHeap.heap[child_id_left], MaxHeap.heap[current_id] = MaxHeap.heap[current_id], MaxHeap.heap[child_id_left] current_id = child_id_left child_id_left = current_id * 2 + 1 child_id_right = current_id * 2 + 2
4、基础堆排序和Heapify
1. 基础排序
有了堆的基本操做,实现堆的排序就比较简单了,用最大堆实现升序排序步骤以下:
a. 将待排序列表依次插入
b. 依次取出堆顶元素并放进原列表对应位置
代码实现以下:
class MaxHeap: heap = [] @staticmethod def insert(num): MaxHeap.heap.append(num) MaxHeap.shift_up() @staticmethod def shift_up(): current_id = len(MaxHeap.heap) - 1 parent_id = (current_id - 1)//2 while current_id > 0: if MaxHeap.heap[parent_id] >= MaxHeap.heap[current_id]: break else: MaxHeap.heap[parent_id], MaxHeap.heap[current_id] = MaxHeap.heap[current_id], MaxHeap.heap[parent_id] current_id = parent_id parent_id = (current_id -1)//2 @staticmethod def delate(num): temp = MaxHeap.heap.pop() ind = MaxHeap.heap.index(num) MaxHeap.heap[ind] = temp MaxHeap.shift_down(ind) @staticmethod def shift_down(ind): current_id = ind child_id_left = current_id * 2 + 1 child_id_right = current_id * 2 + 2 while current_id < len(MaxHeap.heap) - 1: #若是当前节点为叶子节点,shift_down完成 if current_id * 2 + 1 > len(MaxHeap.heap) - 1: break #若是当前节点只有左孩子没有右孩子 if current_id * 2 + 1 == len(MaxHeap.heap) - 1: if MaxHeap.heap[current_id] > MaxHeap.heap[-1]: break else: MaxHeap.heap[current_id], MaxHeap.heap[-1] = MaxHeap.heap[-1], MaxHeap.heap[current_id] break #若是当前节点既有左孩子又有右孩子 if MaxHeap.heap[current_id] > max(MaxHeap.heap[child_id_left], MaxHeap.heap[child_id_right]): break else: if MaxHeap.heap[child_id_right] > MaxHeap.heap[child_id_left]: MaxHeap.heap[child_id_right], MaxHeap.heap[current_id] = MaxHeap.heap[current_id], MaxHeap.heap[child_id_right] current_id = child_id_right child_id_left = current_id * 2 + 1 child_id_right = current_id * 2 + 2 else: MaxHeap.heap[child_id_left], MaxHeap.heap[current_id] = MaxHeap.heap[current_id], MaxHeap.heap[child_id_left] current_id = child_id_left child_id_left = current_id * 2 + 1 child_id_right = current_id * 2 + 2 @staticmethod def extract_max(): num = MaxHeap.heap[0] try: MaxHeap.delate(num) return num except: return num @staticmethod def heap_sort(arr): for n in arr: MaxHeap.insert(n) for i in range(len(arr)): arr[i] = MaxHeap.extract_max()
2. Heapify
基础堆排序中,将n个元素逐个插入到一个空堆中,算法复杂度是O(nlogn)
而下面介绍的Heapify,对n个元素的建堆,算法复杂度是O(n)
Heapify算法过程以下:
----堆的第一个元素从索引0开始,堆元素个数为n
a. 找到待建堆的二叉树最后一个非叶子节点,索引为 m =(n - 1)/2
b. 从索引m到0,依次执行shift_down 操做
二叉树的倒数第一层知足二叉堆性质,所以,从倒数第二层开始,经过shift_down 逐层的将其转换为二叉堆。
代码以下(附带经过heapify的排序算法):
@staticmethod def heapify(arr): MaxHeap.heap = arr n = (len(arr) - 1)//2 while n >= 0: MaxHeap.shift_down(n) n -= 1 @staticmethod def heap_sort2(arr): MaxHeap.heapify(arr) res = [] for i in range(len(arr)): res.append(MaxHeap.extract_max()) return res
5、原地堆排序
在上一节中,不管是堆的基础排序仍是基于heapify的排序,都须要额外的开辟一片空间存放排序。空间复杂度为O(n),
接下来要讲的原地堆排序的空间复杂度为O(1), 算法过程分析以下:
a. 由heapify对n个元素的列表建堆
b. 将堆顶元素与堆尾元素互换,堆大小减一
c. 对堆顶元素执行shift_down操做
d. 依次循环b,c。当堆中元素个数为0时为止
代码以下:
@staticmethod def heap_sort3(arr): MaxHeap.heapify(arr) for i in range(len(arr)-1, -1, -1): MaxHeap.heap[i], MaxHeap.heap[0] = MaxHeap.heap[0], MaxHeap.heap[i] #将堆顶元素与堆尾元素互换 MaxHeap.shift_down(0, i)
6、堆的优点
若使用堆作静态数组的排序,它的时间复杂度与快速排序相比并无优点,实际上通常状况下要慢于快速排序。
那堆排序的优点在哪呢?
堆,在解决动态排序问题时,有较大优点。
问题1. 动态选择优先级最高的任务执行
不少状况下,咱们须要使用优先队列来解决实际问题,如操做系统选择优先级最高的进程使用CPU,而进程随时都会有新进程产生,也会有老进程死亡,并且各进程的优先级也会动态变化。这种时候,若是每次都用排序算法对全部进程优先级进行排序,能够想象耗时是巨大的。而此时堆来解决优先队列就显示出巨大优点,插入新元素,重建最大堆,删除元素,这些操做的时间复杂度均为O(logn)。
问题2. 从N个元素中选出前M个(N巨大而M相对很小,如N=10000000,M=10)
用快速排序算法时间复杂度为NlogN, 而用堆排序时间复杂度为NlogM
固然对于问题2,对快排进行改进,也可提升效率,具体实现方法还没想太清楚。
综上:堆的最大优点就在使用堆实现优先队列。
参考博客: