zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    配置类【RibbonAutoConfiguration】的实例属性【configurations】是如何注入的?---实例化
    配置类【RibbonAutoConfiguration】的实例属性【configurations】是如何注入的?---配置类来源
    为什么启动类被【@SpringBootApplication】注解后,就会自动扫描其包内所有被【@Component】注解的类?
    自定义Ribbon配置时出现自定义的扫描排除注解【excludeFilters】不生效
    Spring
    eureka关闭crsf过滤
    docker-compose
    eureka
    centos7根目录扩容的一些理解
    Error injecting: org.apache.maven.artifact.installer.DefaultArtifactInstaller com.google.inject.ProvisionException: Unable to provision
  • 原文地址:https://www.cnblogs.com/potofsalt/p/14773949.html
Copyright © 2011-2022 走看看