排序算法 基于Javascript
写在前面
个人感觉:javascript对类似排序查找这样的功能已经有了很好的封装,以致于当我们想对数组排序的时候只需要调用
arr.sort()
方法,而查找数组元素也只需要调用indexOf()
方法或lastIndexOf()
方法,我们忽略了其内部的实现。而今,js能开发的项目越来越庞大,对性能和效率要求也越来越高,虽然众多的库和框架也可以帮我们应付这些问题,但小编觉得框架过眼云烟,把握程序开发的基础,才能在飞速的更新换代中应对自如。因此我们不妨也研究一下这些算法,其中的思路有助于我们自身的提高。
声明:本文章中的部分图片来自百度搜索,如侵删。
冒泡排序
这个是最简单的排序,就像气泡从水里冒出来。
它每执行一次外层循环,就会将最小数(或最大的)放到数组最后,然后再寻找剩余部分的最小数(或最大的)放在这一部分的最后,以此类推。
每一个外层循环的过程可以用一下图来描述:
冒泡排序的时间复杂度为O(n2)" role="presentation">O(n2)O(n2),空间复杂度为O(1)" role="presentation">O(1)O(1),属于 稳定 排序。适用于数据比较少或基本有序的情况。
//冒泡排序
bubbleSort = function(arr){
var len = arr.length;
for (var i = 0; i < len; i++){
for (var j = 0; j < len - i - 1; j++){
if (arr[j] > arr[j + 1])
[arr[j + 1], arr[j]] = [arr[j],arr[j + 1]];
}
}
}
选择排序
选择排序也很简单。它每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。下面是完整的选择排序过程:
选择排序的时间复杂度为O(n2)" role="presentation">O(n2)O(n2),空间复杂度为O(1)" role="presentation">O(1)O(1),属于 稳定 排序。适用于数据比较少的情况,综合各种情况来讲还是这个最慢。
//选择排序
selectionSort = function(arr){
var len = arr.length;
var min, min_index;//min每次找到的最小值,min_index最小值在无序序列的位置
for (var i = 0; i < len - 1; i++){
min = arr[i];
for (var j = i + 1; j < len; j++){//找到最小值
if (arr[j] < min){
min = arr[j];//找到的最小值
min_index = j;//找到的最小值索引
}
}
if (min != arr[i])
[arr[min_index], arr[i]] = [arr[i], arr[min_index]];
}
}
插入排序
这个要略微复杂一点了。它的思路就是将一个数据插入到已经排好序的有序数据中,依然保持有序。实现过程把数组看作2部分,一部分是有序的,一部分是无序的,每次大循环将无序数组的第一个元素插入到有序的数组中。
插入排序时间复杂度为O(n2)" role="presentation">O(n2)O(n2),空间复杂度为O(1)" role="presentation">O(1)O(1),属于 稳定 排序。算法适用于少量数据的排序。
注:二分插入和直接插入各种情况复杂度一样
//直接插入排序
insertionSort = function (arr){
var len = arr.length;
var temp;//temp每次要执行插入的值
var index;//index插入值在有序序列的位置
for (var i = 1; i < len; i++){
temp = arr[i];
for (var j = 0; j < i; j++){//找到插入位置
index = i;
if (arr[j] > temp){
index = j;//找到的插入点索引
break;
}
}
if (i != index){
for (var j = i; j > index; j--)//插入该值
[arr[j - 1], arr[j]] = [arr[j],arr[j - 1]];
}
arr[index] = temp;
}
}
快速排序
这个想必大家都耳熟能详,20世纪十大经典算法之一。主要原因还是它极大的推动了信息技术的发展,可惜它不是稳定算法。
这个算法比较就比较难理解了,它通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的任一数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。这里面包含的分治的思想。
下面一个图表现了函数的一次执行过程:
而这个图表现了整个排序过程:
插入排序时间复杂度为O(nlogn)" role="presentation">O(nlogn)O(nlogn),空间复杂度为O(logn)" role="presentation">O(logn)O(logn),属于 不稳定 排序。
////快速排序(前轴)
function quickSort(arr){
qSort(0, arr.length - 1);
return arr;
function qSort(left, right){
if (left >= right)//两个数相遇则结束该轮排序
return;
var key = arr[left];//取最左边的元素作为标识数
var i = left;
var j = right;
while (i != j){//两个数相遇则结束该轮排序
while (i != j && arr[j] >= key) j--;//j前移
[arr[j], arr[i]] = [arr[i], arr[j]];
while (i != j && arr[i] <= key) i++;//i后移
[arr[j], arr[i]] = [arr[i], arr[j]];
}
qSort(left, j - 1);//对标识数前面的数继续该方法排序
qSort(j + 1, right);//对标识数后面的数继续该方法排序
}
}
这里补充一下:快速排序由于其实轴的选择不同,分为前轴、中轴、后轴快速排序,上面的例子是前轴快速排序,下文比较算法的时候也才用上述代码。不过,这里补充另外2种代码:
//中轴快速排序
function quickSortM(arr){
qSort(0, arr.length - 1);
return arr;
function qSort(left, right){
if (left < right){
var index = Math.floor((left + right) / 2);
var key = arr[index];
var i = left - 1;
var j = right + 1;
while (true){
while (arr[++i] < key); // 向右找大于轴的数
while (arr[--j] > key); // 向左找小于轴的数
if (i >= j)//两索引相同结束排序
break;
[arr[i], arr[j]] = [arr[j],arr[i]];//交换找到的数
}
qSort(left, i - 1); // 继续这样对轴前面的排序
qSort(j + 1, right); // 继续这样对轴后面的排序
}
}
}
//后轴快速排序
function quickSortB(arr){
qSort(0, arr.length - 1);
return arr;
function qSort(left, right){
if (left >= right)//两索引相同结束排序
return;
var key = arr[right];
var i = left - 1;//s是最右边的轴
for (var j = left; j < right; j++){ //将数据分成大于轴和小于轴两部分
if (arr[j] <= key){
i++;
[arr[i], arr[j]] = [arr[j],arr[i]];
}
}
i++;
[arr[right], arr[i]] = [arr[i],arr[right]];//将轴插入到大于轴和小于轴两部分的中间
qSort(left, i - 1);//继续这样对轴前面的排序
qSort(i, right);//继续这样对轴后面的排序
}
}
归并排序
这个排序在小编眼里用的是最广泛的,很多函数封装内部都才用这个排序,包括数据库在内的排序也采用了归并排序或红黑树的形式。这个排序也用到了分治的思想:它将一个序列逐级拆分成小序列,将小序列排序后合并,得到完全有序的序列。若每次将序列分成2个子序列,再依此合并,称为二路归并。
没理解?看图:
插入排序时间复杂度为O(nlogn)" role="presentation">O(nlogn)O(nlogn),空间复杂度为O(n)" role="presentation">O(n)O(n),属于 稳定 排序。
//归并排序
function mergeSort(arr){
var temp = [];
merge(0, arr.length - 1);
return arr;
function merge(left, right){//temp是临时空间,存放排序过程中间结果
var mid;//该部分中间位置
if (left >= right)//分组小于等于1时归并结束
return;
mid = Math.floor((left + right) / 2);
merge(left, mid);//对中间位置之前部分继续该方法排序
merge(mid + 1, right);//对中间位置之后部分继续该方法排序
var i = left, j = mid + 1, k = left;
while (i != mid + 1 && j != right + 1)//比较两部分每个值,把较小的放入temp中,并后移该指针,直到某部分全部遍历
temp[k++] = arr[i] < arr[j] ? arr[i++] : arr[j++];
//将未全部遍历部分数据顺次放入temp中
while (i != mid + 1)
temp[k++] = arr[i++];
while (j != right + 1)
temp[k++] = arr[j++];
//将temp复制会a中
for (i = left; i <= right; i++)
arr[i] = temp[i];
}
}
希尔排序
这是惟一一个用人名命名的排序算法。它把数据按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
这个估计最不好理解了,看看图吧:
希尔排序时间复杂度为O(n13)" role="presentation">O(n13)O(n13),空间复杂度为O(1)" role="presentation">O(1)O(1),属于 不稳定 排序。
//希尔排序
shellSort = function(arr){
var len = arr.length;
var index = Math.floor(len / 2);//得到比较步长
var j, temp;
while (index > 0){
for (var i = index; i < len; i++){//遍历起点后移,保证每个数在该步长下参与且只参与1此排序
temp = arr[i];
for (j = i; j >= index && arr[j - index] > temp;){//等步长数列执行插入排序
arr[j] = arr[j - index];
j -= index;
arr[j] = temp;
}
}
index = Math.floor(index / 2);//步长减半
}
}
堆排序
首先说一下一个名词:大根堆。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]]≥A[i]" role="presentation">A[PARENT[i]]≥A[i]A[PARENT[i]]≥A[i], 属于完全二叉树。
根据大根堆的要求可知,最大的值一定在堆顶,根据其特点,如果要求每个节点的左孩子小于右孩子,得到的就是数据从小到大的排列。反之从大到小排列应该使用小根堆。
如果你对二叉树熟悉的话,可以简单用图理解一下
堆排序时间复杂度为O(nlogn)" role="presentation">O(nlogn)O(nlogn),空间复杂度为O(1)" role="presentation">O(1)O(1),属于 不稳定 排序。
下面利用数组快速定位指定索引的元素模拟堆操作,并没有实际建立二叉树。
//堆排序
heapSort = function(arr){
var len = arr.length;
for (var i = len / 2 - 1; i >= 0; i--)//反向遍历数组,将数组调整为大根堆
heapAdjust(arr, i, len);
for (var i = len - 1; i > 0; i--){
[arr[0], arr[i]] = [arr[i], arr[0]];//将无需部分最大数放在最后,即构成有序部分
heapAdjust(arr, 0, i);//将剩余无需部分调整为大根堆,直到该部分只有一个元素为止
}
return arr;
function heapAdjust(arr, i, len){//二叉堆调整函数,负责将堆调整成大根堆(因为是增序排列)
var child;//根孩子的索引
var temp;
//以等倍数间隔,调整堆为大根堆
for (; 2 * i + 1 < len; i = child){
child = 2 * i + 1; //定位其左孩子
if (child < len - 1 && arr[child + 1] > arr[child])//从其左右孩子中选择最大的孩子
child++;
if (arr[i] < arr[child])//如果自己比最大的孩子小,和该孩子交换
[arr[child], arr[i]] = [arr[i], arr[child]];
else
break;
}
}
}
基数排序(桶排序)
这个排序是对费空间的,不过这个思想有点像哈希表的意思。顾名思义,它是透过键值的部份资讯,比如每个数的最高位(如果位数不同在前方补零),将要排序的元素分配至某些“桶”中,依次从低位到高位执行,然后再把每个桶的数据顺序综合起来,以达到排序的作用。就像下图这样,可以理解桶的意思:
下图是整个排序过程示意图:
基数排序时间复杂度为O(d(r+n))" role="presentation">O(d(r+n))O(d(r+n)),空间复杂度为O(rd+n)" role="presentation">O(rd+n)O(rd+n),属于 稳定 排序。(其中r为基数,n为数据总数,d为桶数;也有书得到其平均时间复杂度为O(nlogrd)" role="presentation">O(nlogrd)O(nlogrd))
//基数排序(桶排序)
radixSort = function(arr){
var len = arr.length;
var bullet= [];
var k=1, temp;//k是处理数字的权重,k=1表示处理个位数,k=10表示处理十位数,以此类推
for (var i = 0; i < 10; i++)//为每个桶分配内存空间
bullet[i] = [];
while (true){
var num = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];//num用来统计0~9每个桶里面现有数字个数
for (var i = 0; i < len; i++){//统计分配每个数字到桶里面,并统计每个桶数字个数
temp = Math.floor(arr[i] / k) % 10;
bullet[temp][num[temp]++] = arr[i];
}
if (num[0] == len) break;//当全部数字都在编号为0的桶中,排序结束
//将桶里的数依次放回a数组中
for (var i = 0; i < len; i++){
for (var j = 0; j < 10; j++){
for (var r = 0; r < num[j]; r++)
arr[i++] = bullet[j][r];
}
}
k *= 10;//k增加10倍,从右至左处理下一位数字
}
return arr;
}
排序对比
以上是常见的8种排序算法,小编也把结果写出来把。下面是10个随机数的排序效果:
当然还有算法速度,小编用了2万个均匀分布在0到10000的随机数,得到如下结果:
不过实际使用中,并不是越快越好,而且即便是追求快也和数据本身的质量有关系。就像下面这个表中的:
算法 | 时间复杂度(最好) | 时间复杂度(最好) | 时间复杂度(最坏) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
插入排序 | O(n2)" role="presentation">O(n2)O(n2) | O(n)" role="presentation">O(n)O(n) | O(n2)" role="presentation">O(n2)O(n2) | O(1)" role="presentation">O(1)O(1) | 稳定 |
希尔排序 | O(n1.3)" role="presentation">O(n1.3)O(n1.3) | O(n)" role="presentation">O(n)O(n) | O(n2)" role="presentation">O(n2)O(n2) | O(1)" role="presentation">O(1)O(1) | 不稳定 |
选择排序 | O(n2)" role="presentation">O(n2)O(n2) | O(n2)" role="presentation">O(n2)O(n2) | O(n2)" role="presentation">O(n2)O(n2) | O(1)" role="presentation">O(1)O(1) | 不稳定 |
堆排序 | O(nlog2n)" role="presentation">O(nlog2n)O(nlog2n) | O(nlog2n)" role="presentation">O(nlog2n)O(nlog2n) | O(nlog2n)" role="presentation">O(nlog2n)O(nlog2n) | O(1)" role="presentation">O(1)O(1) | 不稳定 |
冒泡排序 | O(n2)" role="presentation">O(n2)O(n2) | O(n)" role="presentation">O(n)O(n) | O(n2)" role="presentation">O(n2)O(n2) | O(1)" role="presentation">O(1)O(1) | 稳定 |
快速排序 | O(nlog2n)" role="presentation">O(nlog2n)O(nlog2n) | O(nlog2n)" role="presentation">O(nlog2n)O(nlog2n) | O(n2)" role="presentation">O(n2)O(n2) | O(nlog2n)" role="presentation">O(nlog2n)O(nlog2n) | 不稳定 |
归并排序 | O(nlog2n)" role="presentation">O(nlog2n)O(nlog2n) | O(nlog2n)" role="presentation">O(nlog2n)O(nlog2n) | O(nlog2n)" role="presentation">O(nlog2n)O(nlog2n) | O(n)" role="presentation">O(n)O(n) | 稳定 |
基数排序 | O(d(r+n))" role="presentation">O(d(r+n))O(d(r+n)) | O(d(n+rd))" role="presentation">O(d(n+rd))O(d(n+rd)) | O(d(r+n))" role="presentation">O(d(r+n))O(d(r+n)) | O(n+rd)" role="presentation">O(n+rd)O(n+rd) | 稳定 |
注:
1. 基数排序的复杂度中,r" role="presentation">rr 代表关键字基数,d" role="presentation">dd 代表长度,n" role="presentation">nn 代表关键字个数
2. 排序算法的稳定性指在原序列中,ri=rj" role="presentation">ri=rjri=rj,且 ri" role="presentation">riri 在 rj" role="presentation">rjrj 之前,而在排序后的序列中,ri" role="presentation">riri 仍在 rj" role="presentation">rjrj 之前,则称这种排序算法是稳定的;否则称为不稳定的。
开发的时候应该综合排序原始数据状态,需求,稳定性,系统资源等诸多因素来确定使用哪种排序方式,也可一将几种排序组合使用以提高性能,比如小编就发现在快速排序中,当每个部分数据数量小于8时,对每个部分用插入排序就比一直使用快速排序更快。小编在找到一个动图,十分生动形象的表现了不同算法的速度上的差异。
本章js源码可以 点此去下载
排序算法就写这么多,有什么不足还请指点。
排序算法 基于Javascript的更多相关文章
- 转载部长一篇大作:常用排序算法之JavaScript实现
转载部长一篇大作:常用排序算法之JavaScript实现 注:本文是转载实验室同门王部长的大作,找实习找工作在即,本文颇有用处!原文出处:http://www.cnblogs.com/ywang172 ...
- JavaScript 排序算法(JavaScript sorting algorithms)
JavaScrip 排序算法(JavaScript Sorting Algorithms) 基础构造函数 以下几种排序算法做为方法放在构造函数里. function ArrayList () { va ...
- 十大经典排序算法的 JavaScript 实现
计算机领域的都多少掌握一点算法知识,其中排序算法是<数据结构与算法>中最基本的算法之一.排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大 ...
- 六种排序算法的JavaScript实现以及总结
最近几天在系统的复习排序算法,之前都没有系统性的学习过,也没有留下过什么笔记,所以很快就忘了,这次好好地学习一下. 首先说明为了减少限制,以下代码通通运行于Node V8引擎而非浏览器,源码在我的Gi ...
- 常见排序算法基于JS的实现
一:冒泡排序 1. 原理 a. 从头开始比较相邻的两个待排序元素,如果前面元素大于后面元素,就将二个元素位置互换 b. 这样对序列的第0个元素到n-1个元素进行一次遍历后,最大的一个元素就“沉”到序列 ...
- 常用排序算法之JavaScript实现
1.插入排序 1)算法简介 插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法.它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从 后向前扫描,找到相应位置并插入 ...
- 选择排序---直接选择排序算法(Javascript版)
在要排序的一组数列中,选出最小(或者最大)的一个数与第1个位置的数交换:然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个 ...
- 八大排序算法---基于python
本文节选自:http://python.jobbole.com/82270/ 本文用Python实现了插入排序.希尔排序.冒泡排序.快速排序.直接选择排序.堆排序.归并排序.基数排序. 1.插入排序 ...
- 十大经典排序算法(Javascript实现)
前言 总括: 本文结合动图详细讲述了十大经典排序算法用Javascript实现的过程. 原文博客地址:十大经典排序算法 公众号:「菜鸟学前端」,回复「666」,获取一揽子前端技术书籍 人生有情泪沾衣, ...
随机推荐
- Elasticsearch入门系列~通过Java一系列操作Elasticsearch
Elasticsearch索引的创建.数据的增删该查操作 上一章节已经在Linux系统上安装Elasticsearch并且可以外网访问,这节主要通过Java代码操作Elasticsearch 1.创建 ...
- Java网络编程之TCP、UDP
Java网络编程之TCP.UDP 2014-11-25 15:23 513人阅读 评论(0) 收藏 举报 分类: java基础及多线程(28) 版权声明:本文为博主原创文章,未经博主允许不得转载. ...
- 关于python的拷贝
https://blog.csdn.net/koukehui0292/article/details/82993958 Python的 深度拷贝: import copy d=copy.deepcop ...
- JS错误记录 - 按左右箭头div移动、一串div跟着鼠标移动
本次练习错误总结: 1. div跟着用户操作而移动,首先必须要绝对定位,否则无法移动. 2. if条件语句里面是双等号,不是单等号(赋值). 3. 坐标值没有Right,只能offsetLeft 加减 ...
- (转)ORA-00257归档日志写满的解决方法
转自:http://www.cnblogs.com/xwdreamer/p/3804509.html 背景: 在前一篇博客中我们提到了如何启动或关闭oracle的归档(ARCHIVELOG)模式,在我 ...
- E11 css hack
E11 识别\0 { color:red; color:blue \0; } chrome下颜色是红色.IE11是蓝色
- js进阶 13-3 jquery动画显示隐藏,滑动,淡入淡出的本质是什么
js进阶 13-3 jquery动画显示隐藏,滑动,淡入淡出的本质是什么 一.总结 一句话总结:分别改变display,高度,opacity透明度这三种属性. 1.fade系列函数有哪四个? fade ...
- libjpeg用法
libjpeg是一个完全用C语言编写的库,包含了被广泛使用的JPEG解码.JPEG编码和其他的JPEG功能的实现.这个库由独立JPEG工作组维护.最新版本号是6b,于1998年发布.可以参考维基百科关 ...
- LA 3602 - DNA Consensus String 枚举
原题地址:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_ ...
- LA 3882 - And Then There Was One(约瑟夫 递归)
看题传送门 题目大意: N个数排成一圈,第一次删除m,以后每k个数删除一次,求最后一被删除的数. 如果这题用链表或者数组模拟整个过程的话,时间复杂度都将高达O(nk),而n<=10000,k&l ...