图文详解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 相关文章推荐
Packer 3.0 JS压缩及混淆工具 下载
May 03 Javascript
Jquery网页出现的乱码问题的三种解决方法
Jun 30 Javascript
window.open 以post方式传递参数示例代码
Feb 27 Javascript
基于jQuery Bar Indicator 插件实现进度条展示效果
Sep 30 Javascript
详解JavaScript操作HTML DOM的基本方式
Oct 21 Javascript
JS正则表达式验证密码格式的集中情况总结
Feb 23 Javascript
jQuery制作input提示内容(兼容IE8以上)
Jul 05 jQuery
Vue底层实现原理总结
Feb 17 Javascript
微信小程序实现换肤功能
Mar 14 Javascript
JS实现网站吸顶条
Jan 08 Javascript
javascript中可能用得到的全部的排序算法
Mar 05 Javascript
Vue 的双向绑定原理与用法揭秘
May 06 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
使用ThinkPHP自带的Http类下载远程图片到本地的实现代码
2011/08/02 PHP
php中Smarty模板初体验
2011/08/08 PHP
PHP过滤★等特殊符号的正则
2014/01/27 PHP
php中出现空白页的原因及解决方法汇总
2014/07/08 PHP
PHP实现表单提交数据的验证处理功能【防SQL注入和XSS攻击等】
2017/07/21 PHP
用JavaScript实现单继承和多继承的简单方法
2009/03/29 Javascript
JavaScript Event学习第十章 一些可替换的事件对
2010/02/10 Javascript
用js实现table单元格高宽调整,兼容合并单元格(兼容IE6、7、8、FF)实例
2013/06/25 Javascript
JavaScript prototype 使用介绍
2013/08/29 Javascript
jquery滚动到顶部底部代码
2015/04/20 Javascript
jquery实现表单验证并阻止非法提交
2015/07/09 Javascript
使用Jasmine和Karma对AngularJS页面程序进行测试
2016/03/05 Javascript
深入理解js数组的sort排序
2016/05/28 Javascript
关于JS变量和作用域详解
2016/07/28 Javascript
leaflet的开发入门教程
2016/11/17 Javascript
laydate 显示结束时间不小于开始时间的实例
2017/08/11 Javascript
vue使用vuex实现首页导航切换不同路由的方法
2019/05/08 Javascript
javascript写一个ajax自动拦截并下载数据代码实例
2019/09/07 Javascript
Vue-cli3生成的Vue项目加载Mxgraph方法示例
2020/05/31 Javascript
JS实现简单贪吃蛇小游戏
2020/10/28 Javascript
Vue——前端生成二维码的示例
2020/12/19 Vue.js
python中map()与zip()操作方法
2016/02/27 Python
Python多线程threading和multiprocessing模块实例解析
2018/01/29 Python
Python使用sqlalchemy模块连接数据库操作示例
2019/03/13 Python
Python求解正态分布置信区间教程
2019/11/20 Python
python单向循环链表原理与实现方法示例
2019/12/03 Python
sklearn-SVC实现与类参数详解
2019/12/10 Python
pygame实现飞机大战
2020/03/11 Python
python3 自动打印出最新版本执行的mysql2redis实例
2020/04/09 Python
钉钉企业内部H5微应用开发详解
2020/05/12 HTML / CSS
一组SQL面试题
2016/02/15 面试题
.NET是怎么支持多种语言的
2015/02/24 面试题
申报优秀教师材料
2014/12/16 职场文书
部门经理迟到检讨书
2015/02/16 职场文书
自我推荐信怎么写
2015/03/24 职场文书
大学生逃课检讨书
2015/05/04 职场文书