JavaScript 排序,不只是冒泡
转自:http://blog.cdswyda.com/post/javascript/2017-03-22-js-sort-not-only-bubblesort
做编程,排序是个必然的需求。前端也不例外,虽然不多,但是你肯定会遇到。
不过说到排序,最容易想到的就是冒泡排序,选择排序,插入排序了。
冒泡排序
依次比较相邻的两个元素,如果后一个小于前一个,则交换,这样从头到尾一次,就将最大的放到了末尾。
从头到尾再来一次,由于每进行一轮,最后的都已经是最大的了,因此后一轮需要比较次数可以比上一次少一个。虽然你还是可以让他从头到尾来比较,但是后面的比较是没有意义的无用功,为了效率,你应该对代码进行优化。
图片演示如下:
代码实现:
function bubbleSort(arr) {
var len = arr.length;
for (var i = 0; i < len - 1; i++) {
for (var j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j+1]) { // 相邻元素两两对比
var temp = arr[j+1]; // 元素交换
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
选择排序
选择排序我觉得是最简单的了,大一学VB的时候,就只记住了这个排序方法,原理非常简单:每次都找一个最大或者最小的排在开始即可。
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。
代码演示:
function selectionSort(arr) {
var len = arr.length;
var minIndex, temp;
for (var i = 0; i < len - 1; i++) {
minIndex = i;
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { // 寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
插入排序
插入排序也比较简单。就像打扑克一样,依次将拿到的元素插入到正确的位置即可。
1.将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
2.从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
动图演示:
代码示例:
function insertionSort(arr) {
var len = arr.length;
var preIndex, current;
for (var i = 1; i < len; i++) {
preIndex = i - 1;
current = arr[i];
while(preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex+1] = arr[preIndex];
preIndex--;
}
arr[preIndex+1] = current;
}
return arr;
}
简单的代价是低效
上面三种都是非常简单的排序方法,简单的同时呢,效率也会比较低,还是拿这本书里的对比图来说明:
时间复杂度都高达O(n^2),而它们后面的一些排序算法时间复杂度基本都只有O(n log n)。
我的强迫症又犯了,我想要高效率一点的排序方法。
归并排序
简单把这本书的内容过了一遍,当时就理解了这个归并排序,因此这里就谈一下这个归并排序吧。
基本原理是分治法,就是分开并且递归来排序。
步骤如下:
1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4.重复步骤 3 直到某一指针达到序列尾;
5.将另一序列剩下的所有元素直接复制到合并序列尾。
动图演示:
代码示例:
function mergeSort(arr) { // 采用自上而下的递归方法
var len = arr.length;
if(len < 2) {
return arr;
}
var middle = Math.floor(len / 2),
left = arr.slice(0, middle),
right = arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
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());
}
}
while (left.length)
result.push(left.shift());
while (right.length)
result.push(right.shift());
return result;
}
既然是个爱折腾的人,折腾了总得看看效果吧。
效率测试
由于我学这个来进行排序不是对简单数组,数组内都是对象,要对对象的某个属性进行排序,还要考虑升降序。
因此我的代码实现如下:
/**
* [归并排序]
* @param {[Array]} arr [要排序的数组]
* @param {[String]} prop [排序字段,用于数组成员是对象时,按照其某个属性进行排序,简单数组直接排序忽略此参数]
* @param {[String]} order [排序方式 省略或asc为升序 否则降序]
* @return {[Array]} [排序后数组,新数组,并非在原数组上的修改]
*/
var mergeSort = (function() {
// 合并
var _merge = function(left, right, prop) {
var result = [];
// 对数组内成员的某个属性排序
if (prop) {
while (left.length && right.length) {
if (left[0][prop] <= right[0][prop]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
} else {
// 数组成员直接排序
while (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
}
while (left.length)
result.push(left.shift());
while (right.length)
result.push(right.shift());
return result;
};
var _mergeSort = function(arr, prop) { // 采用自上而下的递归方法
var len = arr.length;
if (len < 2) {
return arr;
}
var middle = Math.floor(len / 2),
left = arr.slice(0, middle),
right = arr.slice(middle);
return _merge(_mergeSort(left, prop), _mergeSort(right, prop), prop);
};
return function(arr, prop, order) {
var result = _mergeSort(arr, prop);
if (!order || order.toLowerCase() === 'asc') {
// 升序
return result;
} else {
// 降序
var _ = [];
result.forEach(function(item) {
_.unshift(item);
});
return _;
}
};
})();
需要对哪个属性进行排序是不确定,可以随意指定,因此写成了参数。有由于不想让这些东西在每次循环都进行判断,因此代码有点冗余。
关于降序的问题,也没有加入参数中,而是简单的升序后再逆序输出。原因是不想让每次循环递归里都去判断条件,所以简单处理了。
下面就是见证效率的时候了,一段数据模拟:
var getData = function() {
return Mock.mock({
"list|1000": [{
name: '@cname',
age: '@integer(0,500)'
}]
}).list;
};
上面使用Mock进行了模拟数据,关于Mock : http://mockjs.com/
实际测试来啦:
// 效率测试
var arr = getData();
console.time('归并排序');
mergeSort(arr, 'age');
console.timeEnd('归并排序');
console.time('冒泡排序');
for (var i = 0, l = arr.length; i < l - 1; ++i) {
var temp;
for (var j = 0; j < l - i - 1; ++j) {
if (arr[j].age > arr[j + 1].age) {
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
console.timeEnd('冒泡排序');
进行了五次,效果如下:
// 归并排序: 6.592ms
// 冒泡排序: 25.959ms
// 归并排序: 1.334ms
// 冒泡排序: 20.078ms
// 归并排序: 1.085ms
// 冒泡排序: 16.420ms
// 归并排序: 1.200ms
// 冒泡排序: 16.574ms
// 归并排序: 2.593ms
// 冒泡排序: 12.653ms
最低4倍,最高近16倍的效率之差还是比较满意的。
虽然1000条数据让前端排序的可能性不大,但是几十上百条的情况还是有的。另外由于node,JavaScript也能运行的服务端了,这个效率的提升也还是有用武之地的。
一点疑问
归并排序里面使用了递归,在《数据结构与算法 JavaScript 描述》中,作者给出了自下而上的迭代方法。但是对于递归法,作者却认为:
However, it is not possible to do so in JavaScript, as the recursion goes too deep for the language to handle.
然而,在 JavaScript 中这种方式不太可行,因为这个算法的递归深度对它来讲太深了。
gitbook上这本书的作者对此有疑问,我也有疑问。
归并中虽然用了递归,但是他是放在return后的呀。关于在renturn后的递归是有尾递归优化的呀。
关于尾递归优化是指:本来外层函数内部再调用一个函数的话,由于外层函数需要等待内层函数返回后才能返回结果,进入内层函数后,外层函数的信息,内存中是必须记住的,也就是调用堆栈。而内部函数放在return关键字后,就表示外层函数到此也就结束了,进入内层函数后,没有必要再记住外层函数内的所有信息。
上面是我的理解的描述,不知道算不算准确。chrome下已经可以开启尾递归优化的功能了,我觉得这个递归是不该影响他在JavaScript下的使用的。
最后
有兴趣的话,推荐读读这本书,进行排序的时候,可以考虑一些更高效的方法。
不过需要注意的是,这些高效率的排序方法,一般都需要相对较多的额外内存空间,需要权衡一下。
另外,非常小规模的数据就没有必要了。一是影响太小,而是我们人的效率问题,一分钟能从头写个冒泡、选择、插入的排序方法,而换成是归并排序呢?
---- 广告 ----
掘金是一个高质量的技术社区,从ECMAScript6到Vue.js,性能优化到开源类库,让你不错过前端开发的每一个技术干货。点击链接即可访问掘金官网,或到各大应用市场搜索「掘金」下载APP,技术干货尽在掌握中
JavaScript 排序,不只是冒泡相关推荐
- javascript 排序_JavaScript中的排序方法
javascript 排序 There are tons of sorting algorithms available like bubble sort, merge sort, insertion ...
- php递归算法排序,php常用的排序算法代码[冒泡,递归排序-PHP源码
<script>ec(2);<script> php 常用的排序算法代码[冒泡,递归排序 冒泡排序算法 function bubblesort($arr) { $n=count ...
- Javascript捕捉(capturing)与冒泡(bubbling)的区别
今天,当我在做nodejs的时候刚巧碰到了javascript的冒泡机制.曾经,我是有看过这方面的知识.但是,已经好久没有碰触javascript的内部原理了.所以,有些淡忘了.我又重新做了下研究并记 ...
- javascript排序_鸡尾酒用javascript排序
javascript排序 Just want the code? Scroll all the way down for two versions of the code: 只需要代码? 一直向下滚动 ...
- javascript排序_鸡尾酒在JavaScript中排序
javascript排序 Just want the code? Scroll all the way down for two versions of the code: 只需要代码? 一直向下滚动 ...
- javascript排序算法总结
javascript排序算法总结 这里是对数据结构中的几种算法做一个总结 简单的排序算法有:①冒泡排序 ②选择排序 ③插入排序 高级的排序算法有: ①希尔排序 ②快速排序 一.冒泡排序 如图所示为冒泡 ...
- javascript排序_使用JavaScript对页面内容进行排序
javascript排序 Some of my layout articles, especially those that show items arranged with flexbox or C ...
- 排序算法之冒泡算法的讲解以及此算法的优缺点
**排序算法之冒泡算法的讲解以及此算法的优缺点** **冒泡排序算法是非常常见的一种排序算法,通常用于对数组元素的排序,究竟什么是冒泡排序? ** 一桶水中如果气泡共同处在一个维度或者说处在同一个水平 ...
- javascript数组的排序(sort,冒泡)
转自 https://www.cnblogs.com/kangby/p/6559978.html 一.js的sort()方法 var arr = [10, 20, 1, 2]; arr.sort(fu ...
最新文章
- linux新内核的freeze框架以及意义
- hdu 1175 连连看
- Linux内核 crypto文件夹 密码学知识学习
- linux下调用python脚本,Linux下QT调用Python脚本的解决方案,Qt,python,一种,解决办法
- python 取整_马克的Python学习笔记#数字,日期和时间
- 计算机图形学应用:java2d和3d_?硬核儿童节礼物:MIT学神、太极作者胡渊鸣送你一门计算机课程...
- 《零基础入门学习Python》学习过程笔记【32,33,34异常处理】(没看)
- Atitit 图像处理之理解卷积attilax总结
- 南邮的计算机通信工程课程是什么,通信工程考研详解之南京邮电大学
- 程序员和码农有什么差别?
- Stairway to T-SQL: Beyond The Basics Level 4: Using Views to Simplify Your Query - SQLServerCentral
- 【轻松上手postman】入门篇:如果根据接口文档写postman接口用例
- 1700802088 韩晓忠
- Windows时钟同步问题
- java 16进制与字符串互相转
- 论计算机取证工具软件及其检测(转)
- vue-cli+mock.js+axios模拟前后台数据交互
- Typora开始收费了,这可咋整?
- C语言例题——计算三角形的面积(通过边长或者顶点)
- javaSE-(regex)
热门文章
- OGG/OGV文件格式解析
- 单反相机的传奇—佳能单反50年辉煌之路(连载十五)
- solaris linux 计算磁盘容量 cyl alt sec
- 那些年,我做过的产品:有的死了,有的活了
- 互联网晚报 | 5月13日 星期五 | 罗永浩回应被叫行业冥灯;新一轮汽车下乡政策最快将于本月出台;字节跳动鲸鲮操作系统获批...
- 微信公众平台开发(110) 微信连Wi-Fi
- 使用kubeadm部署k8s(2、k8s集群部署)
- practice是什么意思_practice是什么意思 还有practice的用法
- access如何设置定期报表汇总_Access如何制作复杂报表
- Qt qss 九宫格