归并排序 JavaScript 实现
前文我们了解了快速排序算法的实现,本文我们来了解下另一种流行的排序算法-归并排序算法。
我们先来回顾下快排。快排的核心是找出一个基准元素,把数组中比该元素小的放到左边数组,比该元素大的放到右边数组,如果左边数组和右边数组分别有序,那么leftArray+midItem+rightArray就是我们要的排序结果了。要使得左右数组有序,只需要对它们分别调用快排函数就可以了。递归调用需要一个出口,当数组长度<=1的时候,就是递归出口。
我们再进一步看,其实递归调用的结果形成了一棵二叉树!我们以数组[2, 1, 3, 4, 7, 6, 5]为例,代入数据到之前的快排算法中,堆栈中其实形成了一棵如下二叉树(二叉搜索树):
4
/ \
1 6
\ / \
2 5 7
\
3
当递归到最底层向上回溯时,其实我们只需把父节点和左子树右子树的元素合并成一个数组就行了。而更令人激动的是,左子树的值 <= midItem <= 右子树的值(因为是一棵二叉搜索树)!于是我们只需要简单地将它们按序concat就ok了。
说了这么多,我们回到本文的主题上——归并排序。之所以说到二叉树,是因为归并排序同样可以用构成一棵二叉树来解释,只不过快排的复杂度花在了成树(二叉搜索树)上(从上往下),而归并排序的复杂度花在了归并上(从下往上)。
我们以数组[1, 5, 6, 2, 4, 3]举例,归并排序的第一步,将数组一分为2:
[1, 5, 6] [2, 4, 3]
接着将分成的数组继续一分为2,直到长度为1,我们构成如下二叉树(成树 从上往下):
[1, 5, 6, 2, 4, 3]
/ \
[1, 5, 6] [2, 4, 3]
/ \ / \
[1] [5, 6] [2] [4, 3]
/ \ / \
[5] [6] [4] [3]
当递归到了尽头,我们向上回溯,对于两个有序的数组,我们将它们合并成一个有序数组,从而完成整个归并排序(归并 从下往上):
[1, 2, 3, 4, 5, 6]
/ \
[1, 5, 6] [2, 3, 4]
/ \ / \
[1] [5, 6] [2] [3, 4]
/ \ / \
[5] [6] [4] [3]
代码不难,直接上代码:
function merge(left, right) {
var tmp = [];
while (left.length && right.length) {
if (left[0] < right[0])
tmp.push(left.shift());
else
tmp.push(right.shift());
}
return tmp.concat(left, right);
}
function mergeSort(a) {
if (a.length === 1)
return a;
var mid = ~~(a.length / 2)
, left = a.slice(0, mid)
, right = a.slice(mid);
return merge(mergeSort(left), mergeSort(right));
}
这段合并排序的代码相当简单直观,但是mergeSort()函数会导致很频繁的自调用。一个长度为n的数组最终会调用mergeSort() 2*n-1次,这意味着如果需要排序的数组长度很大会在某些栈小的浏览器上发生栈溢出错误。
这里插个话题,关于递归调用时浏览器的栈大小限制,可以用代码去测试:
var cnt = 0;
try {
(function() {
cnt++;
arguments.callee();
})();
} catch(e) {
console.log(e.message, cnt);
}
// chrome: Maximum call stack size exceeded 35992
// firefox: too much recursion 11953
遇到栈溢出错误并不一定要修改整个算法,只是表明递归不是最好的实现方式。这个合并排序算法同样可以迭代实现,比如(摘抄自《高性能JavaScript》):
function merge(left, right) {
var result = [];
while (left.length && right.length) {
if (left[0] < right[0])
result.push(left.shift());
else
result.push(right.shift());
}
return result.concat(left, right);
}
function mergeSort(a) {
if (a.length === 1)
return a;
var work = [];
for (var i = 0, len = a.length; i < len; i++)
work.push([a[i]]);
work.push([]); // 如果数组长度为奇数
for (var lim = len; lim > 1; lim = ~~((lim + 1) / 2)) {
for (var j = 0, k = 0; k < lim; j++, k += 2)
work[j] = merge(work[k], work[k + 1]);
work[j] = []; // 如果数组长度为奇数
}
return work[0];
}
console.log(mergeSort([1, 3, 4, 2, 5, 0, 8, 10, 4]));
这个版本的mergeSort()函数功能与前例相同却没有使用递归。尽管迭代版本的合并排序算法比递归实现要慢一些,但它并不会像递归版本那样受调用栈限制的影响。把递归算法改用迭代实现是实现栈溢出错误的方法之一。
归并排序 JavaScript 实现的更多相关文章
- JavaScript实现10大算法可视化
参考博客: https://www.cnblogs.com/Unknw/p/6346681.html#4195503 十大经典算法 一张图概括: 名词解释: n:数据规模 k:“桶”的个数 In-pl ...
- JS的十大经典算法排序
引子 有句话怎么说来着: 雷锋推倒雷峰塔,Java implements JavaScript. 当年,想凭借抱Java大腿火一把而不惜把自己名字给改了的JavaScript(原名LiveScript ...
- JS家的排序算法
由于浏览器的原生支持(无需安装任何插件),用JS来学习数据结构和算法也许比c更加便捷些.因为只需一个浏览器就能啪啪啪的调试了.比如下图我学习归并排序算法时,只看代码感觉怎么都理解不了,但是结合chro ...
- js排序算法汇总
JS家的排序算法 十大经典算法排序总结对比 一张图概括: 主流排序算法概览 名词解释: n: 数据规模k:“桶”的个数In-place: 占用常数内存,不占用额外内存Out-place: 占用额外 ...
- JS中常见排序算法详解
本文将详细介绍在JavaScript中算法的用法,配合动图生动形象的让你以最快的方法学习算法的原理以及在需求场景中的用途. 有句话怎么说来着: 雷锋推倒雷峰塔,Java implements Java ...
- js 算法排序总结
1.冒泡排序JavaScript代码实现: function bubbleSort(arr) { var len = arr.length; for (var i = 0; i < len; i ...
- js十大排序算法收藏
十大经典算法排序总结对比 转载自五分钟学算法&https://www.cnblogs.com/AlbertP/p/10847627.html 一张图概括: 主流排序算法概览 名词解释: n: ...
- JS的十大排序算法
名词解释: n: 数据规模k:“桶”的个数In-place: 占用常数内存,不占用额外内存Out-place: 占用额外内存稳定性:排序后2个相等键值的顺序和排序之前它们的顺序相同 冒泡排序(Bub ...
- JS的十大经典算法
冒泡排序(Bubble Sort) 冒泡排序须知: 作为最简单的排序算法之一,冒泡排序给我的感觉就像Abandon在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉...冒泡排序还有一种优化算 ...
随机推荐
- js第三天知识点 循环
/*for(重点) *while *do..while * *3种循环的区别: *for 主要适用于 明确循环次数的 循环 *while 适用于 不明确循环次数的循环 *do..while 适用于 ...
- shell加密工具shc的安装和使用
1) 工具说明 shell脚本是可读写的, 很有可能会泄露敏感信息, 如用户名/密码/路径/IP等. 同样在shell脚本运行时会也泄露敏感信息. shc是一个加密shell脚本的工具, 它的作用是把 ...
- vs调试的时候出错:无法启动程序,操作在当前状态中是非法的
工具--选项--调试--常规--启用asp.net的JavaScript调试(chrome和ie)去掉勾选
- 安装MySQL遇到的常见英文翻译
安装MySQL遇到的常见英文翻译: choose this configuration type to create the optimal server setup for this machine ...
- pycharm 配置支持vue
http://www.cnblogs.com/c-x-m/p/9229199.html
- js对象(BOM部分/DOM部分)
JS总体包括ECMAScript,DOM,BOM三个部分,但是能够和浏览器进行交互的只有DOM和BOM,那么到底什么是DOM和BOM呢 概念 BOM(Browser Object Model)是指浏览 ...
- svn 回滚文件修改
取消对代码的修改分为两种情况: 第一种情况:改动没有被提交(commit). 这种情况下,使用svn revert就能取消之前的修改. svn revert用法如下: # svn revert [ ...
- BZOJ 1791: [IOI2008]Island 岛屿 - 基环树
传送门 题解 题意 = 找出无向基环树森林的每颗基环树的直径. 我们首先需要找到每颗基环树的环, 但是因为是无向图,用tarjan找环, 加个手工栈, 我也是看了dalao的博客才知道tarjan找无 ...
- netty4初步使用
文件 D:\jp\netty\NtServer.java import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Chan ...
- .net使用NPOI的XSSFWorkbook进行web开发中导出Excel
之前也使用过NPOI导出excel,这次是因为在导出的excel里新增了几个列,正好超出了255的限制,所以又要改了. 今天主要出了4个问题: 1. Invalid column index (256 ...