Heap & Heap Sort
---
What is a heap?
首先介绍优先队列:
Heap(堆)是对优先队列的一种实现
每个元素都有对应的key值,且对于大顶堆(Max Heap)而言,其每个元素的key值都 ≥ 子元素的key值
堆可以可视化为一棵完全二叉树:
堆对应的树具有如下性质:(这里层数从0开始)
别忘了之前讲过的完全二叉树的性质:n个结点的完全二叉树高度为 lgn
Max_heapify
max_heapify() 只针对具有 “简单错位” 的堆进行调整,single violation是这里特别强调的一个前提条件
当左右子树均为大顶堆时,显然 left(i) 和 right(i)已经是除开i之外最大的两个数,因此如果 i 比left(i) 和 right(i)还要大,那么以 i为根的树已经是大顶堆;
否则只需比较 i , left(i) 和 right(i) ,将 i 与 max(left(i), right(i))交换位置,这样使得根节点保存最大值,这时三个结点中只有“下沉一层”后的结点 i 不确定是否到达了正确位置
但此时对于 i 而言,其新的左右子树同样是大顶堆,满足 single_violation的假设,因此只需再次对 i 调用max_heapify() 直至整棵树成为大顶堆
===tips:过程图示见末尾 ===
Pre_Condition
注意这里有个重要假设:
- 当对以 i 为根的子树进行 max_heapify() 操作时,结点 i 的左右子树均已经是大顶堆 !
- 因此问题在于: 结点 i 并不是最大值却被放在了 level (lgn - 1) 层上(假设叶子为 level 0,从下往上数)。
那么 max_heapify() 的职责就很显然了:将最上面的结点 i 下沉到正确的位置。
显然影响运行时长的因素在于 i 子树的层数,假设 i 子树深为 lgn,那么max_heapify() 的时间复杂度为O(lgn)
Build_Max_Heap(A)
我们已经知道 max_heapify() 严格要求了左右子树必须为大顶堆,那么对于一个完全乱序的数组A,显然直接调用max_heapify(A)是不允许的,这时要如何将其转换为大顶堆呢?
Algo
Build_Max_Heap(A)将通过多次调用 max_heapify() 将输入A 转换成一个大顶堆:
由于叶子结点没有child,其本身已经是一个大顶堆,满足 single violation
所以我们可以跳过最下层的叶子,从 level 1 开始调用 max_heapify()
(level 1 为叶子结点之上的一层,即倒数第二层)
wait a moment:这里解释一下为什么要从下往上处理:
前面提到了使用 max_heapify() 的前提条件是左右子树已经为大顶堆,因此在倒数第二层调用显然是符合要求的(因为level 1中所有结点的左右子树均已经为大顶堆)
那么在这一层处理完毕后,所有“从level 1长出的子树均被调整为了大顶堆”,也就满足了接下来在level 2中调用max_heapify()的前置条件,以此类推...
接下来继续 Build_Max_Heap(A)的算法思路
我们已经解释了为何循环要从下至上处理,
即 for n/2 downto 1:
不难看出,对于level 1的所有结点,最坏情况下的调整时间也只是O(1)
随着level向上增高,对level L层的结点进行max_heapify操作需要O(L)时间
最上层只有一个根结点,对它的调整需要O(lgn) ---(Tips: 复杂度源于)
将各层所需调整时间加起来可以得到整个算法的时间复杂度为O(n) ,步骤如下:
(括号内为一个收敛级数且收敛为常数)
Build-Max-Heap Demo
给出一个乱序数组 A,通过 Build_Max_Heap(A)将其转化为大顶堆的步骤分解如下:
Heap_Sort
思路步骤:
- 首先将A转化为大顶堆
- 找到最大值,显然为 A[1]
- 将最大值与末尾元素交换,并将A[1]从堆中舍弃(数组length--)
- 对新的大小为 n-1的堆调用max_heapify方法将其调整为大顶堆(因为上一步中已经舍弃了最大值A[1],此时显然满足single violation的前提条件)
- 回到步骤2重复操作直到堆减为空,排序完毕
时间复杂度:
整个算法的开头需要调用一次建立Build-Max-Heap(A) , --- O(n)
此后开始n次迭代: (第一次迭代确定了最大值,第二次确定次大值, ... ,n个数排序完毕需要迭代n次)
每次迭代包括一次swap操作和max_heapify() ---O(lgn)
因此总的时间复杂度为 O(n + nlgn) = O(nlgn)
======
@ max_heapify() 的步骤解析: