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 相关文章推荐
asp.net+js 实现无刷新上传解析csv文件的代码
May 17 Javascript
jquery 使用点滴函数代码
May 20 Javascript
基于jQuery的星级评分插件
Aug 12 Javascript
JavaScript中的style.display属性操作
Mar 27 Javascript
JS 使用for循环遍历子节点查找元素
Sep 06 Javascript
JavaScript如何实现在文本框(密码框)输入提示语
Dec 25 Javascript
Angular工具方法学习
Dec 26 Javascript
react-native-fs实现文件下载、文本存储的示例代码
Sep 22 Javascript
Vue 中使用 CSS Modules优雅方法
Apr 09 Javascript
vue 修改 data 数据问题并实时显示的方法
Aug 27 Javascript
Vue防止白屏添加首屏动画的实例
Oct 31 Javascript
精读《Vue3.0 Function API》
May 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
Zerg基本策略
2020/03/14 星际争霸
德生S2000电路分析
2021/03/02 无线电
解析php中die(),exit(),return的区别
2013/06/20 PHP
PHP中使用asort进行中文排序失效的问题处理
2014/08/18 PHP
浅谈JavaScript中面向对象技术的模拟
2006/09/25 Javascript
jquery 应用代码 方便的排序功能
2010/02/06 Javascript
window.location.hash 属性使用说明
2010/03/20 Javascript
js函数的延迟加载实现代码
2012/10/11 Javascript
Jquery实现控件的隐藏和显示实例
2014/02/08 Javascript
js获取页面引用的css样式表中的属性值方法(推荐)
2016/08/19 Javascript
jQuery实用小技巧_输入框文字获取和失去焦点的简单实例
2016/08/25 Javascript
Nodejs进阶:基于express+multer的文件上传实例
2016/11/21 NodeJs
webpack4 处理SCSS的方法示例
2018/09/03 Javascript
微信小程序如何利用getCurrentPages进行页面传值
2019/07/01 Javascript
vue实现页面滚动到底部刷新
2019/08/16 Javascript
Egg Vue SSR 服务端渲染数据请求与asyncData
2019/11/24 Javascript
javascript实现弹出层效果
2019/12/10 Javascript
微信小程序 SOTER 生物认证DEMO 指纹识别功能
2019/12/13 Javascript
vue中可编辑树状表格的实现代码
2020/10/31 Javascript
Vue3+elementui plus创建项目的方法
2020/12/01 Vue.js
nodejs中使用worker_threads来创建新的线程的方法
2021/01/22 NodeJs
Python中多线程的创建及基本调用方法
2016/07/08 Python
python实现数据预处理之填充缺失值的示例
2017/12/22 Python
python使用sqlite3时游标使用方法
2018/03/13 Python
Python变量类型知识点总结
2019/02/18 Python
详解python算法之冒泡排序
2019/03/05 Python
Django关于admin的使用技巧和知识点
2020/02/10 Python
Pycharm中配置远程Docker运行环境的教程图解
2020/06/11 Python
Python 常用日期处理 -- calendar 与 dateutil 模块的使用
2020/09/02 Python
Pycharm配置lua编译环境过程图解
2020/11/28 Python
canvas 基础之图像处理的使用
2020/04/10 HTML / CSS
雅诗兰黛(Estee Lauder)英国官方网站:世界顶级化妆品牌
2016/12/29 全球购物
为什么需要版本控制?
2013/08/08 面试题
追悼会上的答谢词
2014/01/10 职场文书
2015自愿离婚协议书范本
2015/01/28 职场文书
公司员工体检通知
2015/04/21 职场文书