归并排序 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在单词书里出现的感觉一样,每次都在第一页第一位,所以最熟悉...冒泡排序还有一种优化算 ...
随机推荐
- 通过docker-compose构建ghost博客(一)
通过命令构建ghost博客 docker run -d --name ghost -p : -v $PWD/data:/var/lib/ghost ghost 当然也可以编写yml文件,通过docke ...
- docker-ce-17.09 仓库的创建与使用
docker仓库是集中存放镜像的地方,注册服务器是存放仓库的具体服务器,每个服务器上可以有多个仓库,每个仓库下面有多个镜像. 一.查找仓库中镜像 > docker search centos 二 ...
- MVC防止跨站攻击@Html.AntiForgeryToken()
ASP.NET MVC 中有个标签:@Html.AntiForgeryToken(),需要在页面中加入这个标签,然后在Actoin中加入特性[ValidateAntiForgeryToken]就可以了 ...
- jquery的validate表单验证
html: <form id="reg" action="123.html"> <p class="myerror"> ...
- c# 24种设计模式
备忘录模式(Memento Pattern) 策略模式(Strategy Pattern) 抽象工厂模式(Abstract Factory Pattern) 代理模式(Proxy Pattern) 单 ...
- angular2.0学习笔记3.了解angular2.0项目结构
1.我们应用的代码都位于src文件中,包括所有的组件.模板.样式.图片以及我们的应用所需的任何东西都在这个文件来里. 2.src这个文件夹之外的文件都是为构建应用提供支持用的. src文件夹及用途说明 ...
- PAT 1050 螺旋矩阵(25)(代码)
1050 螺旋矩阵(25)(25 分) 本题要求将给定的N个正整数按非递增的顺序,填入"螺旋矩阵".所谓"螺旋矩阵",是指从左上角第1个格子开始,按顺时针螺旋方 ...
- hihoCoder1159 扑克牌
一道记忆化搜索 原题链接 和着色方案很像,这里就不详细阐述,可以去我博客里的着色方案里看. 但要注意本题不一样的是同种面值的牌花色不同,所以在转移时还需要乘上同种面值的牌的个数. #include&l ...
- win10无法访问别的机器的共享目录
Win + R 输入 regedit Open Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanWorkstat ...
- Spring 系列教程之默认标签的解析
Spring 系列教程之默认标签的解析 之前提到过 Spring 中的标签包括默认标签和自定义标签两种,而两种标签的用法以及解析方式存在着很大的不同,本章节重点带领读者详细分析默认标签的解析过程. 默 ...