堆是数据结构中的一种重要结构,了解了“堆”的概念和操作,可以快速掌握堆排序。

堆的概念

堆是一种特殊的完全二叉树(complete binary tree)。如果一棵完全二叉树的所有节点的值都不小于其子节点,称之为大根堆(或大顶堆);所有节点的值都不大于其子节点,称之为小根堆(或小顶堆)。

在数组(在0号下标存储根节点)中,容易得到下面的式子(这两个式子很重要):

1.下标为i的节点,父节点坐标为(i-1)/2;

2.下标为i的节点,左子节点坐标为2*i+1,右子节点为2*i+2。

堆的建立和维护

堆可以支持多种操作,但现在我们关心的只有两个问题:

1.给定一个无序数组,如何建立为堆?

2.删除堆顶元素后,如何调整数组成为新堆?

先看第二个问题。假定我们已经有一个现成的大根堆。现在我们删除了根元素,但并没有移动别的元素。想想发生了什么:根元素空了,但其它元素还保持着堆的性质。我们可以把最后一个元素(代号A)移动到根元素的位置。如果不是特殊情况,则堆的性质被破坏。但这仅仅是由于A小于其某个子元素。于是,我们可以把A和这个子元素调换位置。如果A大于其所有子元素,则堆调整好了;否则,重复上述过程,A元素在树形结构中不断“下沉”,直到合适的位置,数组重新恢复堆的性质。上述过程一般称为“筛选”,方向显然是自上而下。

删除一个元素是如此,插入一个新元素也是如此。不同的是,我们把新元素放在末尾,然后和其父节点做比较,即自下而上筛选。

那么,第一个问题怎么解决呢?

我看过的数据结构的书很多都是从第一个非叶子结点向下筛选,直到根元素筛选完毕。这个方法叫“筛选法”,需要循环筛选n/2个元素。

但我们还可以借鉴“无中生有”的思路。我们可以视第一个元素为一个堆,然后不断向其中添加新元素。这个方法叫做“插入法”,需要循环插入(n-1)个元素。

由于筛选法和插入法的方式不同,所以,相同的数据,它们建立的堆一般不同。

大致了解堆之后,堆排序就是水到渠成的事情了。

算法概述/思路

我们需要一个升序的序列,怎么办呢?我们可以建立一个最小堆,然后每次输出根元素。但是,这个方法需要额外的空间(否则将造成大量的元素移动,其复杂度会飙升到O(n^2))。如果我们需要就地排序(即不允许有O(n)空间复杂度),怎么办?

有办法。我们可以建立最大堆,然后我们倒着输出,在最后一个位置输出最大值,次末位置输出次大值……由于每次输出的最大元素会腾出第一个空间,因此,我们恰好可以放置这样的元素而不需要额外空间。很漂亮的想法,是不是?

下面是堆排序的示意图(图片来自维基百科):

代码实现

由于堆是一种数据结构,因此,我们可以封装它为一个类。当然,也可以不这么做。下面的代码使用筛选法建立了一个堆。

package flyingcat.sort;

/**

*

* @author FlyingCat

* Date: 2013-8-26

*

*/

public class ArrayHeap {

private int[] array;

public ArrayHeap(int[] arr) {

this.array = arr;

}

private int getParentIndex(int child) {

return (child - 1) / 2;

}

private int getLeftChildIndex(int parent) {

return 2 * parent + 1;

}

/**

* 初始化一个大根堆。

*/

private void initHeap() {

int last = array.length - 1;

for (int i = getParentIndex(last); i >= 0; --i) { // 从最后一个非叶子结点开始筛选

int k = i;

int j = getLeftChildIndex(k);

while (j <= last) {

if (j < last) {

if (array[j] <= array[j + 1]) { // 右子节点更大

j++;

}

}

if (array[k] > array[j]) { //父节点大于子节点中较大者,已经找到最终位置

break; // 停止筛选

} else {

swap(k, j);

k = j; // 继续筛选

}

j = getLeftChildIndex(k);

}// loop while

}// loop i

}

/**

* 调整堆。

*/

private void adjustHeap(int lastIndex) {

int k = 0;

while (k <= getParentIndex(lastIndex)) {

int j = getLeftChildIndex(k);

if (j < lastIndex) {

if (array[j] < array[j + 1]) {

j++;

}

}

if (array[k] < array[j]) {

swap(k, j);

k = j; // 继续筛选

} else {

break; // 停止筛选

}

}

}

/**

* 堆排序。

* */

public void sort() {

initHeap();

int last = array.length - 1;

while (last > 0) {

swap(0, last);

last--;

if (last > 0) { // 这里如果不判断,将造成最终前两个元素逆序。

adjustHeap(last);

}

}

}

private void swap(int i, int j) {

int temp = array[i];

array[i] = array[j];

array[j] = temp;

}

}

算法性能/复杂度

堆排序的时间复杂度非常稳定(我们可以看到,对输入数据不敏感),为O(n㏒n)复杂度,最好情况与最坏情况一样。

但是,其空间复杂度依实现不同而不同。上面即讨论了两种常见的复杂度:O(n)与O(1)。本着节约空间的原则,我推荐O(1)复杂度的方法。

算法稳定性

堆排序存在大量的筛选和移动过程,属于不稳定的排序算法。

算法适用场景

堆排序在建立堆和调整堆的过程中会产生比较大的开销,在元素少的时候并不适用。但是,在元素比较多的情况下,还是不错的一个选择。尤其是在解决诸如“前n大的数”一类问题时,几乎是首选算法。

参考资料

java堆算法_用Java写算法之七:堆排序相关推荐

  1. 【机器学习与算法】python手写算法:Cart树

    [机器学习与算法]python手写算法:Cart树 背景 代码 输出示例 背景 Cart树算法原理即遍历每个变量的每个分裂节点,找到增益(gini或entropy)最大的分裂节点进行二叉分割. 这里只 ...

  2. java 查找排序_查找与排序算法(Java实现)

    1.二分查找算法 package other; public class BinarySearch { /* * 循环实现二分查找算法arr 已排好序的数组x 需要查找的数-1 无法查到数据 */ p ...

  3. 6种java垃圾回收算法_学习java垃圾回收

    垃圾回收(GC)一直是Java受欢迎背后的重要特性之一.垃圾回收是Java中用于释放未使用的内存的机制.本质上,它追踪所有仍在使用的对象,并将剩下的标记为垃圾.Java的垃圾回收被认为是一种自动内存管 ...

  4. java 哈希一致算法_一致哈希算法Java实现

    一致哈希算法(Consistent Hashing Algorithms)是一个分布式系统中常用的算法.传统的Hash算法当槽位(Slot)增减时,面临所有数据重新部署的问题,而一致哈希算法确可以保证 ...

  5. java编写字母z算法_【Java】【每日算法/刷穿 LeetCode】6. Z 字形变换(中等)

    首页 专栏 java 文章详情 0 [每日算法/刷穿 LeetCode]6. Z 字形变换(中等) 宫水三叶发布于 今天 12:03 题目描述 将一个给定字符串 s 根据给定的行数 numRows , ...

  6. 着色问题求解java 图形化_练习 - 实现 Grover 算法以解决图形着色问题

    练习 - 实现 Grover 算法以解决图形着色问题 40 分钟 在本模块中,最后将以端到端的方式实现 Grover 搜索算法:从图形着色问题的黑盒定义到处理算法的随机性质的逻辑. 备注 出于必要,本 ...

  7. java murmurhash实现_一致性哈希算法与Java实现

    一致性哈希算法是分布式系统中常用的算法.比如,一个分布式的存储系统,要将数据存储到具体的节点上,如果采用普通的hash方法,将数据映射到具体的节点上,如key%N,key是数据的key,N是机器节点数 ...

  8. java heap排序_关于Java排序算法-堆排序(Heap Sort)

    堆排序是利用堆的特性进行排序的过程. 堆排序:输出堆顶的最小(大)值后,使剩余的n-1个元素序列重新再建成堆,则可得到原序列的次小(大)值.反复进行可得到一个有序序列,整个过程称为堆排序. 堆排序分为 ...

  9. java飞机场模拟程序_一个java程序模拟race condition的程序,用飞机进港来模拟,自己写了一部分...

    题目是这样的:按照狄更斯算法,算法都在我写的程序里面了,要求两架飞船进港,同时只能是一架飞机进港,他们之间用global参数来进行racecondition.现在我把大体的程序写了一下,我不... 题 ...

  10. java代码课程_助你写出优雅的 Java 代码,8 点建议给你收藏

    我越来越担心我作为一个java程序员的未来.恍然间,发现自己在这个行业里已经摸爬滚打了十年了,原以为自己就凭已有的项目经验和工作经历怎么着也应该算得上是一个业内比较资历的人士了,但是今年在换工作的过程 ...

最新文章

  1. [转载] 七龙珠第一部——第031话 假悟空出现
  2. 普通的旧CLR对象与数据传输对象
  3. Sharepoint带自定义属性的FieldType
  4. 第八节:常见安全隐患和传统的基于Session和Token的安全校验
  5. LeetCode 2120. 执行所有后缀指令(模拟)
  6. Guns导入开发工具
  7. 【数据结构和算法05】 红-黑树(转发)
  8. 2.第一个HTML页面
  9. (17)Node.js第三方模块
  10. Spring-Jpa : @MappedSuperclass的作用
  11. 《深入浅出DPDK》读书笔记(六):报文转发(run to completion、pipeline、精确匹配算法、最长前缀匹配LPM)
  12. java正向最大匹配算法_java中文分词之正向最大匹配法实例代码
  13. 问题六十八:着色模型(shading model)(1)——反射模型(reflection model)(3.1)——辐射学(Radiometry)
  14. 映射网络驱动器失败的解决办法
  15. db2与mysql语法区别_db2和mysql语法的区别是什么
  16. Ubuntu可视化监控温度
  17. 【软件测试之项目实战】
  18. Python-.item()的理解与使用
  19. 国内外最顶级的12大看板工具
  20. 百度地图Javascript API 使用记录

热门文章

  1. DWR中引用JS的路径问题
  2. JBuilder配置JBoss
  3. html未填写提示,文本框输入信息,未输入的文本框会提示输入,并且未输入的文本框会变红...
  4. lisp代码编写地物符号_Aroma:通过结构代码搜索推荐代码
  5. anyview下载java,下载AnyviewMobile Games Java - 596763 - ebook txt Anyview | mobile9
  6. java webmethod_javax.xml.ws.WebServiceException:方法beaInvoke公开为WebMethod
  7. UI设计灵感|购物界面更有吸引力
  8. 游戏迷必备桌面|穿越火线壁纸,燃动少年心
  9. APP引导页UI设计素材模板|轻松留下完美的第一印象
  10. java mysql查询语句_Mysql查询语句执行过程