Heap & Heap Sort

---

What is a heap?

首先介绍优先队列

image-20210512203448892

Heap(堆)是对优先队列的一种实现

每个元素都有对应的key值,且对于大顶堆(Max Heap)而言,其每个元素的key值都 ≥ 子元素的key值

堆可以可视化为一棵完全二叉树:

image-20210512204025555

堆对应的树具有如下性质:(这里层数从0开始)

image-20210512204339594

别忘了之前讲过的完全二叉树的性质:n个结点的完全二叉树高度为 lgn

Max_heapify

image-20210512204519549

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

注意这里有个重要假设:

image-20210516142807629

  • 当对以 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) ,步骤如下:

(括号内为一个收敛级数且收敛为常数)

image-20210512215145581

Build-Max-Heap Demo

给出一个乱序数组 A,通过 Build_Max_Heap(A)将其转化为大顶堆的步骤分解如下:

image-20210516151225124

image-20210516151557850

Heap_Sort

image-20210516151640374

思路步骤:

  1. 首先将A转化为大顶堆
  2. 找到最大值,显然为 A[1]
  3. 将最大值与末尾元素交换,并将A[1]从堆中舍弃(数组length--)
  4. 对新的大小为 n-1的堆调用max_heapify方法将其调整为大顶堆(因为上一步中已经舍弃了最大值A[1],此时显然满足single violation的前提条件)
  5. 回到步骤2重复操作直到堆减为空,排序完毕

时间复杂度

整个算法的开头需要调用一次建立Build-Max-Heap(A) , --- O(n)

此后开始n次迭代: (第一次迭代确定了最大值,第二次确定次大值, ... ,n个数排序完毕需要迭代n次)

每次迭代包括一次swap操作和max_heapify() ---O(lgn)

因此总的时间复杂度为 O(n + nlgn) = O(nlgn)

======

@ max_heapify() 的步骤解析:

image-20210516194644240

原文地址:https://www.cnblogs.com/potofsalt/p/14773949.html