【前端也要学点算法】 归并排序的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实现的更多相关文章
- 前端要不要学数据结构&算法
我们都知道前端开发工程师更多偏向 DOM 渲染和 DOM 交互操作,随之 Node 的推广前端工程师也可以完成服务端开发.对于服务端开发而言大家都觉得数据结构和算法是基础,非学不可.所以正在进行 No ...
- 【前端也要学点算法】快速排序的JavaScript实现
作为算法目录下的第一篇博文,快速排序那是再合适不过了.作为最基本最经典的算法之一,我觉得每个程序员都应该熟悉并且掌握它,而不是只会调用库函数,知其然而不知其所以然. 排序算法有10种左右(或许更多), ...
- python实现折半查找算法&&归并排序算法
今天依旧是学算法,前几天在搞bbs项目,界面也很丑,评论功能好像也有BUG.现在不搞了,得学下算法和数据结构,笔试过不了,连面试的机会都没有…… 今天学了折半查找算法,折半查找是蛮简单的,但是归并排序 ...
- 做acm 需要学的算法
做acm 需要学的算法 转一个搞ACM需要的掌握的算法. 要注意,ACM的竞赛性强,因此自己应该和自己的实际应用联系起来. 适合自己的才是好的,有的人不适合搞算法,喜欢系统架构,因此不要看到别人什 ...
- 经典排序算法 - 归并排序Merge sort
经典排序算法 - 归并排序Merge sort 原理,把原始数组分成若干子数组,对每个子数组进行排序, 继续把子数组与子数组合并,合并后仍然有序,直到所有合并完,形成有序的数组 举例 无序数组[6 2 ...
- 前端开发周报: CSS 布局方式方式与JavaScript数据结构和算法
前端开发周报:CSS 布局方式与JavaScript动画库 1.常见 CSS 布局方式详见: 一些常见的 CSS 布局方式梳理,涉及 Flex 布局.Grid 布局.圣杯布局.双飞翼布局等.http: ...
- web前端入坑第二篇:web前端到底怎么学?干货资料! 【转】
http://blog.csdn.net/xllily_11/article/details/52145172 版权声明:本文为博主[小北]原创文章,如要转载请评论回复.个人前端公众号:前端你别闹,J ...
- 数据结构和算法(Golang实现)(23)排序算法-归并排序
归并排序 归并排序是一种分治策略的排序算法.它是一种比较特殊的排序算法,通过递归地先使每个子序列有序,再将两个有序的序列进行合并成一个有序的序列. 归并排序首先由著名的现代计算机之父John_von_ ...
- 第十四章 web前端开发小白学爬虫
老猿从事IT开发快三十年了,接触互联网也很久了,但自己没有做过web前端开发,只知道与前端开发相关的一些基本概念,如B/S架构.html标签.js脚本.css样式.xml解析.cookies.http ...
随机推荐
- Java Gradle入门指南之插件管理(类型、导入及java plugin使用)
上一篇随笔介绍了如何使用Gradle内建任务,介绍了自定义Gradle任务类的三种方法(build文件,buildSrc文件夹.新建groovy项目),一个任务是一个原子操作,即不可分割的.项 ...
- Linux简介及常用命令使用2--linux常用命令:查看 删除 编辑 创建等
cd . // 进入当前目录 cd ~ //进入根目录 pwd //当前路径 echo "my name is makaidong">makai ...
- mysql5.6源码安装
1.环境介绍: 包:mysql-5.6.24.tar.gz 平台:centos6.5 2.安装cmake编译工具和依赖包: yum install cmake -y yum install ncurs ...
- asp.net 后台 Http POST请求
时间忙,简单些,直接贴代码上图 百度站长平台为站长提供链接提交通道,您可以提交想被百度收录的链接,百度搜索引擎会按照标准处理 http://zhanzhang.baidu.com/linksubmit ...
- 利用NuSoap开发WebService(PHP)
利用NuSoap开发WebService(PHP) 分类: php 2010-09-08 12:00 5005人阅读 评论(1) 收藏 举报 webservicephpsoapstringencodi ...
- union all 与order by的连用
昨天工作过程中发现一个奇怪的地方: 代码段A: 1---select * from table1 2---order by no 3---union all 4---select * ...
- 在Solr中配置和使用ansj分词
在上一节[编译Ansj之Solr插件]中介绍如何编译ansj分词在solr(lucene)环境中使用的接口,本章将介绍如何在solr中使用ansj,其步骤主要包括:下载或者编译ansj和nlp-lan ...
- 深入理解FTP协议
文件传输协议FTP(File Transfer Protocol)是因特网中使用最广泛的文件传输协议.FTP使用交互式的访问,允许客户指定文件的类型和格式(如指明是否使用ASCII码),并允许文件具有 ...
- HDU 4630 No Pain No Game 树状数组+离线操作
题意:给一串数字,每次查询[L,R]中两个数的gcd的最大值. 解法:容易知道,要使取两个数让gcd最大,这两个数最好是倍数关系,所以处理出每个数的所有倍数,两两间根据倍数关系形成一条线段,值为该数. ...
- Activity的onCreate()的PersistableBundle 参数坑。
Bundle 与 PersistableBundle 区别 仅仅是Activity oncreate()的一个参数与两个参数的区别: @Override public void onCreate(Bu ...