JS中的算法与数据结构之常见排序(Sort)算法详解


Posted in Javascript onAugust 16, 2019

本文实例讲述了JS中的算法与数据结构之常见排序(Sort)算法。分享给大家供大家参考,具体如下:

排序算法(Sort)

引言

我们平时对计算机中存储的数据执行的两种最常见的操作就是排序和查找,对于计算机的排序和查找的研究,自计算机诞生以来就没有停止过。如今又是大数据,云计算的时代,对数据的排序和查找的速度、效率要求更高,因此要对排序和查找的算法进行专门的数据结构设计,(例如我们上一篇聊到的二叉查找树就是其中一种),以便让我们对数据的操作更加简洁高效。

这一篇我们将会介绍一些数据排序的基本算法和高级算法并利用JavaScript来逐一实现,让大伙对计算机中常见的排序算法的思想和实现有基本的了解,起到一个抛砖引玉的作用。

关于排序算法的说明

在介绍各个算法之前,我们有必要了解一下评估算法优劣的一些术语:

稳定:如果a原本在b前面,当a=b时,排序之后a仍然在b的前面 不稳定:如果a原本在b的前面,当a=b时,排序之后a可能会出现在b的后面

内排序:所有排序操作都在内存中完成 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行

时间复杂度:一个算法执行所耗费的时间 空间复杂度:运行完一个程序所需内存的大小

有想要了解更多,关于时间空间复杂度的,我推荐一篇文章,请戳这里

基本排序算法

基本排序算法的核心思想就是对一组数据按照一定的顺序重新排序,其中重排时一般都会用到一组嵌套的 for 循环,外循环会遍历数组的每一项元素,内循环则用于进行元素直接的比较。

1.冒泡排序(BubbleSort)

冒泡排序是比较经典的算法之一,也是排序最慢的算法之一,因为它的实现是非常的容易的。

冒泡排序的算法思想如下(升序排序):

  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样最终最大数被交换到最后的位置
  3. 除了最后一个元素以外,针对所有的元素重复以上的步骤
  4. 重复步骤1~3,直到排序完成

下面我借用网上一张动图,来展示冒泡排序的过程:

JS中的算法与数据结构之常见排序(Sort)算法详解冒泡排序

具体的JS实现如下:

//冒泡排序
function bubbleSort ( data ) {
 var temp = 0;
 for ( var i = data.length ; i > 0 ; i -- ){
  for( var j = 0 ; j < i - 1 ; j++){
   if( data[j] > data[j + 1] ){
    temp = data[j];
    data[j] = data [j+1];
    data[j+1] = temp;
   }
  }
 }
 return data;
}

我们先设定一组数据,后面我们将都用这组数据来测试 :

var dataStore = [ 72 , 1 , 68 , 95 , 75 , 54 , 58 , 10 , 35 , 6 , 28 , 45 , 69 , 13 , 88 , 99 , 24 , 28 , 30 , 31 , 78 , 2 , 77 , 82 , 72 ];

console.log( '原始数据:' + dataStore );
console.log( '冒泡排序:' + bubbleSort( dataStore) );

// 原始数据:72,1,68,95,75,54,58,10,35,6,28,45,69,13,88,99,24,28,30,31,78,2,77,82,72
// 冒泡排序:1,2,6,10,13,24,28,28,30,31,35,45,54,58,68,69,72,72,75,77,78,82,88,95,99

2.选择排序(SelctionSort)

选择排序是一种比较简单直观的排序算法。它的算法思想是,从数组的开头开始遍历,将第一个元素和其他元素分别进行比较,记录最小的元素,等循环结束之后,将最小的元素放到数组的第一个位置上,然后从数组的第二个位置开始继续执行上述步骤。当进行到数组倒数第二个位置的时候,所有的数据就完成了排序。

选择排序同样会用到嵌套循环,外循环从数组第一个位置移到倒数第二个位置;内循环从第二个位置移动到数组最后一个位置,查找比当前外循环所指向的元素还要小的元素,每次内循环结束后,都会将最小的值放到合适的位置上。

同样,我借用网上一张动图,来展示选择排序的过程 :

JS中的算法与数据结构之常见排序(Sort)算法详解选择排序

了解了算法思想,具体实现应该也不成问题:

//选择排序
function selectionSort( data ) {
 for( var i = 0; i< data.length ; i++){
  var min = data[i];
  var temp;
  var index = i;
  for( var j = i + 1; j< data.length; j++){
   if( data[j] < min ){
    min = data[j];
    index = j;
   }
  }

  temp = data[i];
  data[i] = min;
  data[index]= temp;
 }
 return data;
}

它的测试结果如下:

console.log( '原始数据:' + dataStore );
console.log( '选择排序:' + selectionSort( dataStore) );

// 原始数据:72,1,68,95,75,54,58,10,35,6,28,45,69,13,88,99,24,28,30,31,78,2,77,82,72
// 选择排序:1,2,6,10,13,24,28,28,30,31,35,45,54,58,68,69,72,72,75,77,78,82,88,95,99

3.插入排序(insertionSort)

插入排序有点类似人类按字母顺序对数据进行排序,就如同你打扑克牌一样,将摸来的扑克按大小放到合适的位置一样。它的原理就是通过嵌套循环,外循环将数组元素挨个移动,而内循环则对外循环中选中的元素及它后面的元素进行比较;如果外循环中选中的元素比内循环中选中的元素小,那么数组元素会向右移动,为内循环中的这个元素腾出位置。

实现步骤如下:

  1. 从第一个元素开始,该元素默认已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置
  6. 重复步骤2~5,直到排序完成

它的实现效果图如下:

JS中的算法与数据结构之常见排序(Sort)算法详解插入排序

具体实现代码如下:

//插入排序

function insertionSort( data ) {
 var len = data.length;
 for (var i = 1; i < len; i++) {
  var key = data[i];
  var j = i - 1;
  while ( j >= 0 && data[j] > key) {
   data[j + 1] = data[j];
   j--;
  }
  data[j + 1] = key;
 }
 return data;
}

排序结果如下:

console.log( '原始数据:' + dataStore );
console.log( '插入排序:' + insertionSort( dataStore) );

// 原始数据:72,1,68,95,75,54,58,10,35,6,28,45,69,13,88,99,24,28,30,31,78,2,77,82,72
// 插入排序:1,2,6,10,13,24,28,28,30,31,35,45,54,58,68,69,72,72,75,77,78,82,88,95,99

我们已经学习了三种基本的排序算法,其中冒泡排序是最慢的,插入排序是最快的,我们可以在运行的过程中通过 console.time('sortName') 和 console.timeEnd('sortName') 两个输出来看他们的效率如何,我这里给出一组值作为参考,实际中需要大量的数据测试和反复实验,进行数理统计后才能被视为有效的统计;

JS中的算法与数据结构之常见排序(Sort)算法详解 排序时间比较

高级排序算法

4.希尔排序(Shell Sort)

我们首先要学习的就是希尔排序,又称缩小增量排序,这个算法是在插入排序的基础上做了很大的改善,与插入排序不同的是,它首先会比较位置较远的元素,而非相邻的元素。这种方案可以使离正确位置很远的元素能够快速回到合适的位置,当算法进行遍历时,所有元素的间距会不断的减小,直到数据的末尾,此时比较的就是相邻元素了。

该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上有较大提高。

好吧,我还是用个案例来解释,这样会更清晰,我们以下面一组数据为例:

JS中的算法与数据结构之常见排序(Sort)算法详解 数据集

  • 第一次 gap(增量) = 10 / 2 = 5 , 会按照下面进行分组得到五组数据(49,13)、(38,27)、(65,49)、(97,55)、(26,4),这样进行组内排序之后(13,49)、(27,38)、(49,65)、(55,97)、(4,26)

JS中的算法与数据结构之常见排序(Sort)算法详解 第一次分组

此时,数据会排成如下结构

JS中的算法与数据结构之常见排序(Sort)算法详解 第一次排序

  • 第二次 gap = 5 / 2 = 2 , 此时可以得到两个分组,如下

JS中的算法与数据结构之常见排序(Sort)算法详解 第二次分组

再通过组内排序之后,可以得到

JS中的算法与数据结构之常见排序(Sort)算法详解 第二次排序

  • 第三次 gap = 2 / 2 = 1 , 即不用分组,进行排序后

JS中的算法与数据结构之常见排序(Sort)算法详解 第三次排序

  • 第四次 gap = 1 / 2 = 0 ,即可得到排序完成的数组

JS中的算法与数据结构之常见排序(Sort)算法详解 排序完成

现在,可能对希尔排序有了一定得了解了,用JS实现如下:

//希尔排序

function shallSort(array) {
 var increment = array.length;
 var i
 var temp; //暂存
 do {
  //设置增量
  increment = Math.floor(increment / 3) + 1;
  for (i = increment ; i < array.length; i++) {
   if ( array[i] < array[i - increment]) {
    temp = array[i];
    for (var j = i - increment; j >= 0 && temp < array[j]; j -= increment) {
     array[j + increment] = array[j];
    }
    array[j + increment] = temp;
   }
  }
 }
 while (increment > 1)

 return array;
}

效果如下:

console.log( '原始数据:' + dataStore );
console.log( '希尔排序:' + shallSort( dataStore) );

// 原始数据:72,1,68,95,75,54,58,10,35,6,28,45,69,13,88,99,24,28,30,31,78,2,77,82,72
// 希尔排序:1,2,6,10,13,24,28,28,30,31,35,45,54,58,68,69,72,72,75,77,78,82,88,95,99

5.归并排序(Merge Sort)

将两个的有序数列合并成一个有序数列,我们称之为"归并",归并排序的思想就是将一系列排序好的子序列合并成一个大的完整有序的序列。

实现步骤如下:

  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 将两个排序好的子序列合并成一个最终的排序序列

一张动图来说明归并排序的过程:

JS中的算法与数据结构之常见排序(Sort)算法详解归并排序

具体的JS代码实现如下:

//归并排序

function mergeSort ( array ) {
 var len = array.length;
 if( len < 2 ){
  return array;
 }
 var middle = Math.floor(len / 2),
  left = array.slice(0, middle),
  right = array.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;
}

测试结果如下 :

console.log( '原始数据:' + dataStore );
console.log( '希尔排序:' + mergeSort( dataStore) );

// 原始数据:72,1,68,95,75,54,58,10,35,6,28,45,69,13,88,99,24,28,30,31,78,2,77,82,72
// 希尔排序:1,2,6,10,13,24,28,28,30,31,35,45,54,58,68,69,72,72,75,77,78,82,88,95,99

6.快速排序(Quicksort)

快速排序是处理大数据最快的排序算法之一,它也是一种分而治之的算法,通过递归方式将数据依次分解为包含较小元素和较大元素的不同子序列,会不断重复这个步骤,直到所有的序列全部为有序的,最后将这些子序列一次拼接起来,就可得到排序好的数据。

该算法首先要从数列中选出一个元素作为基数(pivot)。接着所有的数据都将围绕这个基数进行,将小于改基数的元素放在它的左边,大于或等于它的数全部放在它的右边,对左右两个小数列重复上述步骤,直至各区间只有1个数。

整个排序过程如下:

JS中的算法与数据结构之常见排序(Sort)算法详解 快速排序

具体实现如下:

//快速排序

function quickSort( arr ){
 if ( arr.length == 0) {
  return [];
 }
 var left = [];
 var right = [];
 var pivot = arr[0];
 for (var i = 1; i < arr.length; i++) {
  if (arr[i] < pivot) {
   left.push( arr[i] );
  } else {
   right.push( arr[i] );
  }
 }
 return quickSort( left ).concat( pivot, quickSort( right ));
}

测试结果如下:

console.log( '原始数据:' + dataStore );
console.log( '快速排序:' + quickSort( dataStore) );

// 原始数据:72,1,68,95,75,54,58,10,35,6,28,45,69,13,88,99,24,28,30,31,78,2,77,82,72
// 快速排序:1,2,6,10,13,24,28,28,30,31,35,45,54,58,68,69,72,72,75,77,78,82,88,95,99

至此,我们已基本介绍过一些常见的排序算法的思想和具体实现(基数排序在之前的文章已经介绍过),排序算法博大精深,我们不仅要学习理论,也要不断去实践,大家加油!

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.3water.com/code/HtmlJsRun测试上述代码运行效果。

Javascript 相关文章推荐
超级酷和最实用的jQuery实例收集(20个)
Apr 21 Javascript
写了10年的Javascript也未必全了解的连续赋值运算
Mar 25 Javascript
jQuery写的日历(包括日历的样式及功能)
Apr 23 Javascript
window.location的重写及判断location是否被重写
Sep 04 Javascript
Javascript基础教程之关键字和保留字汇总
Jan 18 Javascript
javascript正则表达式模糊匹配IP地址功能示例
Jan 06 Javascript
Javascript blur与click冲突解决办法
Jan 09 Javascript
yii form 表单提交之前JS在提交按钮的验证方法
Mar 15 Javascript
vue中element组件样式修改无效的解决方法
Feb 03 Javascript
javascript实现获取一个日期段内每天不同的价格(计算入住总价格)
Feb 05 Javascript
关于Vue项目跨平台运行问题的解决方法
Sep 18 Javascript
vue项目打包部署_nginx代理访问方法详解
Sep 20 Javascript
jQuery zTree插件使用简单教程
Aug 16 #jQuery
微信小程序 如何保持登录状态
Aug 16 #Javascript
JS事件流与事件处理程序实例分析
Aug 16 #Javascript
javascript事件监听与事件委托实例详解
Aug 16 #Javascript
使用Vue-Awesome-Swiper实现旋转叠加轮播效果&amp;平移轮播效果
Aug 16 #Javascript
详解Vue中组件传值的多重实现方式
Aug 16 #Javascript
详解vue+axios给开发环境和生产环境配置不同的接口地址
Aug 16 #Javascript
You might like
php中$_REQUEST、$_POST、$_GET的区别和联系小结
2011/11/23 PHP
php指定函数参数默认值示例代码
2013/12/04 PHP
Yii2超好用的日期和时间组件(值得收藏)
2016/05/05 PHP
php原生导出excel文件的两种方法(推荐)
2016/11/19 PHP
php + nginx项目中的权限详解
2017/05/23 PHP
基于jQuery的输入框无值自动显示指定数据的实现代码
2011/01/24 Javascript
JavaScript 类型的包装对象(Typed Wrappers)
2011/10/27 Javascript
让你的博客飘雪花超出屏幕依然看得见
2013/01/04 Javascript
jQuery中Dom的基本操作小结
2014/01/23 Javascript
javascript下拉框选项单击事件的例子分享
2015/03/04 Javascript
jquery实现简单的遮罩层
2016/01/08 Javascript
详解Js模板引擎(TrimPath)
2016/11/22 Javascript
jQuery特殊符号转义的实现
2016/11/30 Javascript
javascript基本数据类型及类型检测常用方法小结
2016/12/14 Javascript
JavaScript实现时钟滴答声效果
2017/01/29 Javascript
jQuery插件zTree实现单独选中根节点中第一个节点示例
2017/03/08 Javascript
JavaScript之Canvas_动力节点Java学院整理
2017/07/04 Javascript
Servlet3.0与纯javascript通过Ajax交互的实例详解
2018/03/18 Javascript
openlayers4.6.5实现距离量测和面积量测
2020/09/25 Javascript
[03:00]《DAC最前线》之欧美新秀VS老将
2015/02/01 DOTA
在Python中使用SimpleParse模块进行解析的教程
2015/04/11 Python
Django框架中处理URLconf中特定的URL的方法
2015/07/20 Python
python算法表示概念扫盲教程
2017/04/13 Python
使用 Python 玩转 GitHub 的贡献板(推荐)
2019/04/04 Python
python3.8与pyinstaller冲突问题的快速解决方法
2020/01/16 Python
如何把python项目部署到linux服务器
2020/08/26 Python
彪马法国官网:PUMA法国
2019/12/15 全球购物
美国床垫连锁店:Mattress Firm
2021/02/13 全球购物
土木工程应届生求职信
2013/10/31 职场文书
工程专业毕业生自荐信范文
2013/12/25 职场文书
关于读书的演讲稿
2014/05/07 职场文书
ktv好的活动方案
2014/08/15 职场文书
高中课前三分钟演讲稿
2014/08/18 职场文书
员工手册编写范本
2015/05/14 职场文书
庆元旦主持词
2015/07/06 职场文书
在SQL Server中使用 Try Catch 处理异常的示例详解
2022/07/15 SQL Server