精通八大排序算法系列:二、堆排序算法

作者:July 、二零一一年二月二十日
本文参考:Introduction To Algorithms,second edition。
-------------------

此精通排序算法系列,前一节,已讲过了一、快速排序算法,其中,快速排序每一趟比较用时O(n),要进行lgn次比较,才最终完成整个排序。所以快排的复杂度才为O(n*lgn)。而本节,我们要讲的是堆排序算法。据我所知,要真正彻底认识一个算法,最好是去查找此算法的原发明者的论文或相关文献。

ok,此节,咱们开始吧。

一、堆排序算法的基本特性
时间复杂度:O(nlgn)...
//等同于归并排序
最坏:O(nlgn)
空间复杂度:O(1).
不稳定。

二、堆与最大堆的建立
要介绍堆排序算法,咱们得先从介绍堆开始,然后到建立最大堆,最后才讲到堆排序算法。

堆的介绍
    如下图,

a),就是一个堆,它可以被视为一棵完全二叉树。
每个堆对应于一个数组b),假设一个堆的数组A,
我们用length[A]表述数组中的元素个数,heap-size[A]表示本身存放在A中的堆的元素个数。
当然,就有,heap-size[A]<=length[A]。

树的根为A[1],i表示某一结点的下标,
则父结点为PARENT(i),左儿子LEFT[i],右儿子RIGHT[i]的关系如下:

PARENT(i)
   return |_i/2_|

LEFT(i)
   return 2i

RIGHT(i)
   return 2i + 1

二叉堆根据根结点与其子结点的大小比较关系,分为最大堆和最小堆。
最大堆:
根以外的每个结点i都不大于其根结点,即根为最大元素,在顶端,有
     A[PARENT(i)] ≥ A[i] ,

最小堆:
根以外的每个结点i都不小于其根结点,即根为最小元素,在顶端,有
     A[PARENT(i)] ≤ A[i] .

在本节的堆排序算法中,我们采用的是最大堆;最小堆,通常在构造最小优先队列时使用。

由前面,可知,堆可以看成一棵树,所以,堆的高度,即为树的高度,O(lgn)。
所以,一般的操作,运行时间都是为O(lgn)。

具体,如下:
The MAX-HEAPIFY:O(lgn)  这是保持最大堆的关键.
The BUILD-MAX-HEAP:线性时间。在无序输入数组基础上构造最大堆。
The HEAPSORT:O(nlgn) time, 堆排序算法是对一个数组原地进行排序.
The MAX-HEAP-INSERT, HEAP-EXTRACT-MAX, HEAP-INCREASE-KEY, HEAP-MAXIMUM:O(lgn)。
可以让堆作为最小优先队列使用。

保持堆的性质(O(lgn))

为了保持最大堆的性质,我们运用MAX-HEAPIFY操作,递归调用此操作,使i为根的子树成为最大堆。

MAX-HEAPIFY算法,如下所示:

MAX-HEAPIFY(A, i)
 1 l ← LEFT(i)
 2 r ← RIGHT(i)
 3 if l ≤ heap-size[A] and A[l] > A[i]
 4    then largest ← l
 5    else largest ← i
 6 if r ≤ heap-size[A] and A[r] > A[largest]
 7    then largest ← r
 8 if largest ≠ i
 9    then exchange A[i] <-> A[largest]
10         MAX-HEAPIFY(A, largest)

如上,首先第一步,在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。如果A[i]已经就是最大的元素,则程序直接结束。否则,i的某个子结点为最大的元素,将其,即A[largest]与A[i]交换,从而使i及其子女都能满足最大堆性质。下标largest所指的元素变成了A[i]的值,会违反最大堆性质,所以对largest所指元素调用MAX-HEAPIFY。如下,是此MAX-HEAPIFY的演示过程:

由上图,我们很容易看出,初始构造出一最大堆之后,在元素A[i],即16,大于它的俩个子结点4、10,满足最大堆性质。所以,i下调指向着4,小于,左子14,所以,调用MAX-HEAPIFY,4与其子,14交换位置。但4处在了14原来的位置之后,4小于其右子8,又违反了最大堆的性质,所以再递归调用MAX-HEAPIFY,将4与8,交换位置。于是,满足了最大堆性质,程序结束。

MAX-HEAPIFY的运行时间
   MAX-HEAPIFY作用在一棵以结点i为根的、大小为n的子树上时,其运行时间为调整元素A[i]、A[LEFT(i)],A[RIGHT(i)]的关系时所用时间为O(1),再加上,对以i的某个子结点为根的子树调用MAX-HEAPIFY所需的时间,且i结点的子树大小至多为2n/3,所以,MAX-HEAPIFY的运行时间为
     T (n) ≤ T(2n/3) + Θ(1).

我们,可以证得此式子的递归解为T(n)=O(lgn)。具体证法,可参考算法导论第6章之6.2节,这里,略过。

建堆(O(N))

BUILD-MAX-HEAP(A)
1  heap-size[A] ← length[A]
2  for i ← |_length[A]/2_| downto 1
3       do MAX-HEAPIFY(A, i)    //建堆,怎么建列?原来就是不断的调用MAX-HEAPIFY(A, i)来建立最大堆。

BUILD-MAX-HEAP通过对每一个其它结点,都调用一次MAX-HEAPIFY,
来建立一个与数组A[1...n]相对应的最大堆。A[(|_n/2_|+1) ‥ n]中的元素都是树中的叶子。
因此,自然而然,每个结点,都可以看作一个只含一个元素的堆。

关于此过程BUILD-MAX-HEAP(A)的正确性,可参考算法导论 第6章之6.3节。
下图,是一个此过程的例子:

BUILD-MAX-HEAP的运行时间
       因为每次调用MAX-HEAPPIFY的时间为O(lgn),而共有O(n)次调用,所以BUILD-MAX-HEAP的简单上界为O(nlgn)。算法导论一书提到,尽管这个时间界是对的,但从渐进意义上,还不够精确。

那么,更精确的时间界,是多少列?
由于,MAX-HEAPIFY在树中不同高度的结点处运行的时间不同,且大部分结点的高度都比较小,
而我们知道,一n个元素的堆的高度为|_lgn_|(向下取整),且在任意高度h上,至多有|-n/2^h+1-|(向上取整)个结点。

因此,MAX-HEAPIFY作用在高度为h的结点上的时间为O(h),所以,BUILD-MAX-HEAP的上界为:O(n)。具体推导过程,略。

三、堆排序算法

所谓的堆排序,就是调用上述俩个过程:一个建堆的操作、BUILD-MAX-HEAP,一个保持最大堆的操作、MAX-HEAPIFY。详细算法如下:

HEAPSORT(A)    //n-1次调用MAX-HEAPIFY,所以,O(n*lgn)
1 BUILD-MAX-HEAP(A)      //建最大堆,O(n)
2 for i ← length[A] downto 2
3    do exchange A[1] <-> A[i]
4       heap-size[A] ← heap-size[A] - 1
5       MAX-HEAPIFY(A, 1)    //保持堆的性质,O(lgn)

如上,即是堆排序算法的完整表述。下面,再贴一下上述堆排序算法中的俩个建堆、与保持最大堆操作:
BUILD-MAX-HEAP(A)  //建堆
1  heap-size[A] ← length[A]
2  for i ← |_length[A]/2_| downto 1
3       do MAX-HEAPIFY(A, i)

MAX-HEAPIFY(A, i)     //保持最大堆
 1 l ← LEFT(i)
 2 r ← RIGHT(i)
 3 if l ≤ heap-size[A] and A[l] > A[i]
 4    then largest ← l
 5    else largest ← i
 6 if r ≤ heap-size[A] and A[r] > A[largest]
 7    then largest ← r
 8 if largest ≠ i
 9    then exchange A[i] <-> A[largest]
10         MAX-HEAPIFY(A, largest)

以下是,堆排序算法的演示过程(通过,顶端最大的元素与最后一个元素不断的交换,交换后又不断的调用MAX-HEAPIFY以重新维持最大堆的性质,最后,一个一个的,从大到小的,把堆中的所有元素都清理掉,也就形成了一个有序的序列。这就是堆排序的全部过程。):

上图中,a->b,b->c,....之间,都有一个顶端最大元素与最小元素交换后,调用MAX-HEAPIFY的过程,我们知道,此MAX-HEAPIFY的运行时间为O(lgn),而要完成整个堆排序的过程,共要经过O(n)次这样的MAX-HEAPIFY操作。所以,才有堆排序算法的运行时间为O(n*lgn)。

完。

本人July对本博客所有任何文章、内容和资料享有版权。
转载务必注明作者本人及出处,并通知本人。二零一一年二月二十一日。

转载于:https://www.cnblogs.com/v-July-v/archive/2011/02/21/1983675.html

精通八大排序算法系列:二、堆排序算法相关推荐

  1. 算法系列(二):贪心算法--Huffman编码

    算法系列(二):贪心算法--Huffman编码 一.分析 问题描述: 哈夫曼编码是广泛地用于数据文件压缩的十分有效的编码方法.其压缩率通常在20%-90%之间.哈夫曼编码算法使用字符在文件中出现的频率 ...

  2. 【算法系列 二】Stack

    为什么80%的码农都做不了架构师?>>>    栈应用的场景: 1.括号问题 2.后缀表达式 3.深度优先遍历 4.保存现场 1. 给定字符串,仅由"()[]{}" ...

  3. 查找算法系列之复杂算法:哈希查找

    查找算法系列之复杂算法:哈希查找 眼下为止已经介绍了顺序查找.二分查找.分块查找.二叉排序树.见作者之前的文章: http://blog.csdn.net/u010025211/article/det ...

  4. 【算法系列】卡尔曼滤波算法

    系列文章目录 ·[算法系列]卡尔曼滤波算法 ·[算法系列]非线性最小二乘求解-直接求解法 ·[算法系列]非线性最小二乘求解-梯度下降法 ·[算法系列]非线性最小二乘-高斯牛顿法 ·[算法系列]非线性最 ...

  5. 算法-详解堆排序算法

    title: 算法-详解堆排序算法 date: 2017-07-06 22:00:16 categories: 算法,面试 tags: [算法,Algorithm,面试,排序] description ...

  6. 程序设计与算法(二)算法基础-郭炜 1.3.1 称硬币

    程序设计与算法(二)算法基础-郭炜 1.3.1 称硬币 有12枚硬币.其中有11枚真币和1枚假币.假币和真币重量不同,但不知道假币比真币轻还是重.现在,用一架天平称了这些币三次,告诉你称的结果,请你找 ...

  7. 排序算法系列:归并排序算法

    概述 上一篇我们说了一个非常简单的排序算法--选择排序.其复杂程序完全是冒泡级的,甚至比冒泡还要简单.今天要说的是一个相对比较复杂的排序算法--归并排序.复杂的原因不仅在于归并排序分成了两个部分进行解 ...

  8. 上标3下标6算法_图解堆排序算法

    堆排序定义 一般来说,算法就像数学公式,前人经过不断优化和验证得到有规律性的公式留给后人使用,当然也会交给后人验证的思路.那么堆排序算法就是这样,它有基本固定的定义如下: 1.将数组构建为一颗有规则的 ...

  9. 分治法的关键特征_算法系列之常用算法之一----分治算法

    一.基本概念 在计算机科学中,分治法是一种很重要的算法.分治算法,字面上的解释是"分而治之",分治算法主要是三点: 1.将一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问 ...

最新文章

  1. Facebook Docusaurus 中文文档 自定义页面
  2. linux算术表达式求值数据结构,数据结构:算数表达式求值演示
  3. Pentium II Pentium III架构/微架构/流水线 (3) - P6执行核详解 (指令时延吞吐量,执行单元/发射口)
  4. 安装SQL Server 那些事儿
  5. 有钱人的学习能力,穷人该得好好模仿
  6. 函数体中的return以及函数的解的二分法
  7. 中国兽医显微镜市场趋势报告、技术动态创新及市场预测
  8. Android中如何查看内存(下)
  9. 条款40:慎重的选择多重继承
  10. catia v5r18 百度云_CATIA V5 R18全套的教程.pdf
  11. Agilent/安捷伦N6705B直流电源分析仪
  12. PCB设计经典资料学习汇总
  13. php依赖注入框架pimple中文文档
  14. 淘宝天猫自动领取喵币
  15. 十个著名悖论的最终解答 (转载)
  16. CS231n公开课系列1_视频+课件+作业下载+GitHub批量下载工具
  17. BZOJ3730 震波+BZOJ4372 烁烁的游戏(动态点分治)
  18. MFC修改界面图标时,已导入.ico文件,但是程序运行后,界面不显示更新后的新图标
  19. 2020放弃安卓用鸿蒙,放弃鸿蒙用安卓,认输就在一念之间,网友13亿人养不起华为...
  20. HDMI原理详解以及时序流程(视频是三对差分信号,音频Audio是PCM级(无压缩)传输,包含在数据包内,依靠协议规定采样)HDMI可以传输RGB与YUV两种格式

热门文章

  1. jsonp数据库拿数据到html页面,前端如何优雅的使用jsonp获取接口数据
  2. js遍历追加html子样式,前端基本功:JS(十一)动画封装(CSS样式获取、JSON遍历)...
  3. c++ 异步下获取线程执行结果_异步编排(CompletableFuture异步调用)
  4. Android React Native 笔记(一):基础
  5. Linux上新建用户及赋权操作
  6. 数据预处理-异常值识别
  7. bazel编译tensorflow 生成libtensorflow_inference.so 和 libandroid_tensorflow_inference_java.jar
  8. 宠物乘机的三种模式【转】
  9. spark出现task不能序列化错误的解决方法 org.apache.spark.SparkException: Task not serializable...
  10. electron node 版本不匹配的解决细节