图文详解Heap Sort堆排序算法及JavaScript的代码实现


Posted in Javascript onMay 04, 2016

1. 不得不说说二叉树
要了解堆首先得了解一下二叉树,在计算机科学中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
二叉树的每个结点至多只有二棵子树(不存在度大于 2 的结点),二叉树的子树有左右之分,次序不能颠倒。二叉树的第 i 层至多有 2i - 1 个结点;深度为 k 的二叉树至多有 2k - 1 个结点;对任何一棵二叉树 T,如果其终端结点数为 n0,度为 2 的结点数为 n2,则n0 = n2 + 1。
树和二叉树的三个主要差别:
树的结点个数至少为 1,而二叉树的结点个数可以为 0
树中结点的最大度数没有限制,而二叉树结点的最大度数为 2
树的结点无左、右之分,而二叉树的结点有左、右之分
二叉树又分为完全二叉树(complete binary tree)和满二叉树(full binary tree)
满二叉树:一棵深度为 k,且有 2k - 1 个节点称之为满二叉树

图文详解Heap Sort堆排序算法及JavaScript的代码实现

(深度为 3 的满二叉树 full binary tree)
完全二叉树:深度为 k,有 n 个节点的二叉树,当且仅当其每一个节点都与深度为 k 的满二叉树中序号为 1 至 n 的节点对应时,称之为完全二叉树

图文详解Heap Sort堆排序算法及JavaScript的代码实现

(深度为 3 的完全二叉树 complete binary tree)
2. 什么是堆?
堆(二叉堆)可以视为一棵完全的二叉树,完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示(普通的一般的二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。
如下图,是一个堆和数组的相互关系

图文详解Heap Sort堆排序算法及JavaScript的代码实现

(堆和数组的相互关系)
对于给定的某个结点的下标 i,可以很容易的计算出这个结点的父结点、孩子结点的下标:
Parent(i) = floor(i/2),i 的父节点下标
Left(i) = 2i,i 的左子节点下标
Right(i) = 2i + 1,i 的右子节点下标

图文详解Heap Sort堆排序算法及JavaScript的代码实现

二叉堆一般分为两种:最大堆和最小堆。
最大堆:
最大堆中的最大元素值出现在根结点(堆顶)
堆中每个父节点的元素值都大于等于其孩子结点(如果存在)

图文详解Heap Sort堆排序算法及JavaScript的代码实现

(最大堆)
最小堆:
最小堆中的最小元素值出现在根结点(堆顶)
堆中每个父节点的元素值都小于等于其孩子结点(如果存在)

图文详解Heap Sort堆排序算法及JavaScript的代码实现

(最小堆)
3. 堆排序原理
堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。在堆中定义以下几种操作:
最大堆调整(Max-Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
创建最大堆(Build-Max-Heap):将堆所有数据重新排序,使其成为最大堆
堆排序(Heap-Sort):移除位在第一个数据的根节点,并做最大堆调整的递归运算
继续进行下面的讨论前,需要注意的一个问题是:数组都是 Zero-Based,这就意味着我们的堆数据结构模型要发生改变

图文详解Heap Sort堆排序算法及JavaScript的代码实现

(Zero-Based)
相应的,几个计算公式也要作出相应调整:
Parent(i) = floor((i-1)/2),i 的父节点下标
Left(i) = 2i + 1,i 的左子节点下标
Right(i) = 2(i + 1),i 的右子节点下标
最大堆调整(MAX?HEAPIFY)的作用是保持最大堆的性质,是创建最大堆的核心子程序,作用过程如图所示:

图文详解Heap Sort堆排序算法及JavaScript的代码实现

(Max-Heapify)
由于一次调整后,堆仍然违反堆性质,所以需要递归的测试,使得整个堆都满足堆性质,用 JavaScript 可以表示如下:

/**
 * 从 index 开始检查并保持最大堆性质
 *
 * @array
 *
 * @index 检查的起始下标
 *
 * @heapSize 堆大小
 *
 **/
function maxHeapify(array, index, heapSize) {
 var iMax = index,
   iLeft = 2 * index + 1,
   iRight = 2 * (index + 1);

 if (iLeft < heapSize && array[index] < array[iLeft]) {
  iMax = iLeft;
 }

 if (iRight < heapSize && array[iMax] < array[iRight]) {
  iMax = iRight;
 }

 if (iMax != index) {
  swap(array, iMax, index);
  maxHeapify(array, iMax, heapSize); // 递归调整
 }
}

function swap(array, i, j) {
 var temp = array[i];
 array[i] = array[j];
 array[j] = temp;
}

通常来说,递归主要用在分治法中,而这里并不需要分治。而且递归调用需要压栈/清栈,和迭代相比,性能上有略微的劣势。当然,按照20/80法则,这是可以忽略的。但是如果你觉得用递归会让自己心里过不去的话,也可以用迭代,比如下面这样:

/**
 * 从 index 开始检查并保持最大堆性质
 *
 * @array
 *
 * @index 检查的起始下标
 *
 * @heapSize 堆大小
 *
 **/
function maxHeapify(array, index, heapSize) {
 var iMax, iLeft, iRight;
 while (true) {
  iMax = index;
  iLeft = 2 * index + 1;
  iRight = 2 * (index + 1);
  if (iLeft < heapSize && array[index] < array[iLeft]) {
   iMax = iLeft;
  }

  if (iRight < heapSize && array[iMax] < array[iRight]) {
   iMax = iRight;
  }

  if (iMax != index) {
   swap(array, iMax, index);
   index = iMax;
  } else {
   break;
  }
 }
}

function swap(array, i, j) {
 var temp = array[i];
 array[i] = array[j];
 array[j] = temp;
}

创建最大堆(Build-Max-Heap)的作用是将一个数组改造成一个最大堆,接受数组和堆大小两个参数,Build-Max-Heap 将自下而上的调用 Max-Heapify 来改造数组,建立最大堆。因为 Max-Heapify 能够保证下标 i 的结点之后结点都满足最大堆的性质,所以自下而上的调用 Max-Heapify 能够在改造过程中保持这一性质。如果最大堆的数量元素是 n,那么 Build-Max-Heap 从 Parent(n) 开始,往上依次调用 Max-Heapify。流程如下:

图文详解Heap Sort堆排序算法及JavaScript的代码实现

用 JavaScript 描述如下:

function buildMaxHeap(array, heapSize) {
 var i,
   iParent = Math.floor((heapSize - 1) / 2);
   
 for (i = iParent; i >= 0; i--) {
  maxHeapify(array, i, heapSize);
 }
}

堆排序(Heap-Sort)是堆排序的接口算法,Heap-Sort先调用Build-Max-Heap将数组改造为最大堆,然后将堆顶和堆底元素交换,之后将底部上升,最后重新调用Max-Heapify保持最大堆性质。由于堆顶元素必然是堆中最大的元素,所以一次操作之后,堆中存在的最大元素被分离出堆,重复n-1次之后,数组排列完毕。整个流程如下:

图文详解Heap Sort堆排序算法及JavaScript的代码实现

用 JavaScript 描述如下:

function heapSort(array, heapSize) {

 buildMaxHeap(array, heapSize);

 for (int i = heapSize - 1; i > 0; i--) {
  swap(array, 0, i);
  maxHeapify(array, 0, i);
 } 
}

4.JavaScript 语言实现
最后,把上面的整理为完整的 javascript 代码如下:

function heapSort(array) {

 function swap(array, i, j) {
  var temp = array[i];
  array[i] = array[j];
  array[j] = temp;
 }

 function maxHeapify(array, index, heapSize) {
  var iMax,
   iLeft,
   iRight;
  while (true) {
   iMax = index;
   iLeft = 2 * index + 1;
   iRight = 2 * (index + 1);

   if (iLeft < heapSize && array[index] < array[iLeft]) {
    iMax = iLeft;
   }

   if (iRight < heapSize && array[iMax] < array[iRight]) {
    iMax = iRight;
   }

   if (iMax != index) {
    swap(array, iMax, index);
    index = iMax;
   } else {
    break;
   }
  }
 }

 function buildMaxHeap(array) {
  var i,
   iParent = Math.floor(array.length / 2) - 1;

  for (i = iParent; i >= 0; i--) {
   maxHeapify(array, i, array.length);
  }
 }

 function sort(array) {
  buildMaxHeap(array);

  for (var i = array.length - 1; i > 0; i--) {
   swap(array, 0, i);
   maxHeapify(array, 0, i);
  }
  return array;
 }

 return sort(array);
}

5.堆排序算法的运用

(1)算法性能/复杂度
堆排序的时间复杂度非常稳定(我们可以看到,对输入数据不敏感),为O(n?n)复杂度,最好情况与最坏情况一样。
但是,其空间复杂度依实现不同而不同。上面即讨论了两种常见的复杂度:O(n)与O(1)。本着节约空间的原则,我推荐O(1)复杂度的方法。

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

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

Javascript 相关文章推荐
lib.utf.js
Aug 21 Javascript
ExtJS 2.0实用简明教程 之Ext类库简介
Apr 29 Javascript
jWiard 基于JQuery的强大的向导控件介绍
Oct 28 Javascript
纯js实现无限空间大小的本地存储
Jun 18 Javascript
Bootstrap实现提示框和弹出框效果
Jan 11 Javascript
JS条形码(一维码)插件JsBarcode用法详解【编码类型、参数、属性】
Apr 19 Javascript
ES6中参数的默认值语法介绍
May 03 Javascript
input框中自动展示当前日期yyyy/mm/dd的实现方法
Jul 06 Javascript
vue-cli 3.0 自定义vue.config.js文件,多页构建的方法
Sep 19 Javascript
详解@Vue/Cli 3 Invalid Host header 错误解决办法
Jan 02 Javascript
vue 使用element-ui中的Notification自定义按钮并实现关闭功能及如何处理多个通知
Aug 17 Javascript
VUE 动态组件的应用案例分析
Dec 02 Javascript
一分钟理解js闭包
May 04 #Javascript
学JavaScript七大注意事项【必看】
May 04 #Javascript
Jquery ui datepicker设置日期范围,如只能隔3天【实现代码】
May 04 #Javascript
开启BootStrap学习之旅
May 04 #Javascript
JavaScript入门教程之引用类型
May 04 #Javascript
javascript和jquery实现用户登录验证
May 04 #Javascript
基于Bootstrap使用jQuery实现简单可编辑表格
May 04 #Javascript
You might like
比特率,大家看看这个就不用收音机音质去比MP3音质了
2021/03/01 无线电
PHP中抽象类和抽象方法概念与用法分析
2016/05/24 PHP
php rmdir使用递归函数删除非空目录实例详解
2016/10/20 PHP
微信小程序 消息推送php服务器验证实例详解
2017/03/30 PHP
PHP有序表查找之插值查找算法示例
2018/02/10 PHP
Laravel 模型使用软删除-左连接查询-表起别名示例
2019/10/24 PHP
JavaScript中数组的排序、乱序和搜索实现代码
2011/11/30 Javascript
没有document.getElementByName方法
2013/08/19 Javascript
Javascript添加监听与删除监听用法详解
2014/12/19 Javascript
Javascript 拖拽的一些高级的应用(逐行分析代码,让你轻松了拖拽的原理)
2015/01/23 Javascript
JavaScript常用函数工具集:lao-utils
2016/03/01 Javascript
Kindeditor在线文本编辑器如何过滤HTML
2016/04/14 Javascript
简单的JS轮播图代码
2016/07/18 Javascript
巧用jQuery选择器提高写表单效率的方法
2016/08/19 Javascript
使用Javascript监控前端相关数据的代码
2016/10/27 Javascript
JavaScript实现二分查找实例代码
2017/02/22 Javascript
nodejs 如何手动实现服务器
2018/08/20 NodeJs
Vue组件内部实现一个双向数据绑定的实例代码
2019/04/04 Javascript
javascript Canvas动态粒子连线
2020/01/01 Javascript
element跨分页操作选择详解
2020/06/29 Javascript
js+h5 canvas实现图片验证码
2020/10/11 Javascript
如何在vue 中引入使用jquery
2020/11/10 jQuery
[03:20]2015国际邀请赛全明星表演赛
2015/08/08 DOTA
python在linux中输出带颜色的文字的方法
2014/06/19 Python
python多线程实现同时执行两个while循环的操作
2020/05/02 Python
python爬取股票最新数据并用excel绘制树状图的示例
2021/03/01 Python
CSS3系列教程:背景图片(背景大小和多背景图) 应用说明
2012/12/19 HTML / CSS
html5实现多文件的上传示例代码
2014/02/13 HTML / CSS
HTML5 解决苹果手机不能自动播放音乐问题
2017/12/27 HTML / CSS
伦敦一家非常流行的时尚精品店:Oxygen Boutique
2017/01/15 全球购物
加拿大租车网站:Enterprise Rent-A-Car
2018/07/26 全球购物
自荐信格式简述
2014/01/25 职场文书
放飞梦想演讲稿600字
2014/08/26 职场文书
2014大学生职业生涯规划书最新范文
2014/09/13 职场文书
党员个人总结范文
2015/02/14 职场文书
2015年度绩效考核工作总结
2015/05/27 职场文书