JS中的算法与数据结构——排序(Sort)(转)
排序算法(Sort)
引言
我们平时对计算机中存储的数据执行的两种最常见的操作就是排序和查找,对于计算机的排序和查找的研究,自计算机诞生以来就没有停止过。如今又是大数据,云计算的时代,对数据的排序和查找的速度、效率要求更高,因此要对排序和查找的算法进行专门的数据结构设计,(例如我们上一篇聊到的二叉查找树就是其中一种),以便让我们对数据的操作更加简洁高效。
这一篇我们将会介绍一些数据排序的基本算法和高级算法并利用JavaScript来逐一实现,让大伙对计算机中常见的排序算法的思想和实现有基本的了解,起到一个抛砖引玉的作用。
关于排序算法的说明
在介绍各个算法之前,我们有必要了解一下评估算法优劣的一些术语:
稳定:如果a原本在b前面,当a=b时,排序之后a仍然在b的前面
不稳定:如果a原本在b的前面,当a=b时,排序之后a可能会出现在b的后面
内排序:所有排序操作都在内存中完成
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行
时间复杂度:一个算法执行所耗费的时间
空间复杂度:运行完一个程序所需内存的大小
有想要了解更多,关于时间空间复杂度的,我推荐一篇文章,请戳这里这里
基本排序算法
基本排序算法的核心思想就是对一组数据按照一定的顺序重新排序,其中重排时一般都会用到一组嵌套的 for 循环,外循环会遍历数组的每一项元素,内循环则用于进行元素直接的比较。
1.冒泡排序(BubbleSort)
冒泡排序是比较经典的算法之一,也是排序最慢的算法之一,因为它的实现是非常的容易的。
冒泡排序的算法思想如下(升序排序):
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
 - 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样最终最大数被交换到最后的位置
 - 除了最后一个元素以外,针对所有的元素重复以上的步骤
 - 重复步骤1~3,直到排序完成
 
下面我借用网上一张动图,来展示冒泡排序的过程:
具体的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)
选择排序是一种比较简单直观的排序算法。它的算法思想是,从数组的开头开始遍历,将第一个元素和其他元素分别进行比较,记录最小的元素,等循环结束之后,将最小的元素放到数组的第一个位置上,然后从数组的第二个位置开始继续执行上述步骤。当进行到数组倒数第二个位置的时候,所有的数据就完成了排序。
选择排序同样会用到嵌套循环,外循环从数组第一个位置移到倒数第二个位置;内循环从第二个位置移动到数组最后一个位置,查找比当前外循环所指向的元素还要小的元素,每次内循环结束后,都会将最小的值放到合适的位置上。
同样,我借用网上一张动图,来展示选择排序的过程 :
了解了算法思想,具体实现应该也不成问题:
//选择排序
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)
插入排序有点类似人类按字母顺序对数据进行排序,就如同你打扑克牌一样,将摸来的扑克按大小放到合适的位置一样。它的原理就是通过嵌套循环,外循环将数组元素挨个移动,而内循环则对外循环中选中的元素及它后面的元素进行比较;如果外循环中选中的元素比内循环中选中的元素小,那么数组元素会向右移动,为内循环中的这个元素腾出位置。
实现步骤如下:
- 从第一个元素开始,该元素默认已经被排序
 - 取出下一个元素,在已经排序的元素序列中从后向前扫描
 - 如果该元素(已排序)大于新元素,将该元素移到下一位置
 - 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
 - 将新元素插入到该位置
 - 重复步骤2~5,直到排序完成
 
它的实现效果图如下:
具体实现代码如下:
//插入排序
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') 两个输出来看他们的效率如何,我这里给出一组值作为参考,实际中需要大量的数据测试和反复实验,进行数理统计后才能被视为有效的统计;
高级排序算法
4.希尔排序(Shell 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)
 
此时,数据会排成如下结构
- 第二次 gap = 5 / 2 = 2 , 此时可以得到两个分组,如下
 
再通过组内排序之后,可以得到
- 第三次 gap = 2 / 2 = 1 , 即不用分组,进行排序后
 
- 第四次 gap = 1 / 2 = 0 ,即可得到排序完成的数组
 
现在,可能对希尔排序有了一定得了解了,用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)
将两个的有序数列合并成一个有序数列,我们称之为"归并",归并排序的思想就是将一系列排序好的子序列合并成一个大的完整有序的序列。
实现步骤如下:
- 把长度为n的输入序列分成两个长度为n/2的子序列;
 - 对这两个子序列分别采用归并排序;
 - 将两个排序好的子序列合并成一个最终的排序序列
 
一张动图来说明归并排序的过程:
具体的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个数。
整个排序过程如下:
具体实现如下:
//快速排序
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
至此,我们已基本介绍过一些常见的排序算法的思想和具体实现(基数排序在之前的文章已经介绍过,想要了解戳这里),排序算法博大精深,我们不仅要学习理论,也要不断去实践,大家加油!
JS中的算法与数据结构——排序(Sort)(转)的更多相关文章
- JS中的算法与数据结构——排序(Sort)
		
排序算法(Sort) 引言 我们平时对计算机中存储的数据执行的两种最常见的操作就是排序和查找,对于计算机的排序和查找的研究,自计算机诞生以来就没有停止过.如今又是大数据,云计算的时代,对数据的排序和查 ...
 - JS中常见算法问题
		
JS中常见算法问题 1. 阐述JS中的变量提升(声明提前) 答:将所有的变量提升当当前作用域的顶部,赋值留在原地.意味着我们可以在某个变量声明前就使用该变量. 虽然JS会进行变量提升,但并不会执行真正 ...
 - Linux内核中的算法和数据结构
		
算法和数据结构纷繁复杂,但是对于Linux Kernel开发人员来说重点了解Linux内核中使用到的算法和数据结构很有必要. 在一个国外问答平台stackexchange.com的Theoretica ...
 - js 中的算法题,那些经常看到的
		
js中遇到的算法题不是很多,可以说基本遇不到.但面试的时候,尤其是一些大公司,总是会出这样那样的算法题,考察一个程序员的逻辑思维能力.如下: 1.回文. 回文是指把相同的词汇或句子,在下文中调换位置或 ...
 - js中数组Array对象的方法sort()的应用
		
一. sort()方法的介绍 //给一组数据排序 var arrNum = [12,1,9,23,56,100,88,66]; console.log("排序前的数组:"+arrN ...
 - JS中json数组多字段排序方法(解决兼容性问题)(转)
		
前端对一个json数组进行排序,用户需要动态的根据自己的选择来对json数据进行排序. 由于后台表设计问题所以不能用sql进行排序,这里用到了js的sort方法. 如果对单字段排序,那么很简单,一个s ...
 - js中的数组Array定义与sort方法使用示例
		
Array的定义及sort方法使用示例 Array数组相当于java中的ArrayList 定义方法: 1:使用new Array(5 )创建数组 var ary = new Array(5): ...
 - js中常见算法
		
一.面试80%都要问的数组去重 数组去重的方式有多种,其实面试中主要是想靠对对象的理解.还记得我第一次去面试的时候,去重的时候用了2个for循环. //1循环一次 var arr1 = [1,2,3, ...
 - cocos2d JS 中的数组拼接与排序
		
var arrA = [];//创建三个局部变量的新数组 var arrB = []; var arrC = []; var newCards = this.MyMahjong;//创建一个新的局部变 ...
 
随机推荐
- php中的echo,json_decode,json_encode常用函数使用注意事项
			
---恢复内容开始--- 1.echo函数 echo只能输出单个字符串或者整数,不能直接输出数组.要输出多个字符串必须用分号 eg: echo可以输出字符串加变量,如果输出的数字字符串则会将对应的数字 ...
 - Java中的集合框架(下)
			
学生选课--判断Set中课程是否存在 package com.imooc.collection; import java.util.ArrayList; import java.util.Arrays ...
 - Codeforces 714A Meeting of Old Friends
			
A. Meeting of Old Friends time limit per test:1 second memory limit per test:256 megabytes input:sta ...
 - HDU 1728 逃离迷宫(DFS经典题,比赛手残写废题)
			
逃离迷宫 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
 - Vjios P1736 铺地毯【暴力,思维】
			
铺地毯 描述 为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看做是平面直角坐标系的第一象限)铺上一些矩形地毯.一共有n张地毯,编号从1到n.现在将这些地毯按照编号从小到大的顺序平行于坐标轴 ...
 - hdu_2669 Romantic(扩展欧几里得)
			
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2669 Romantic Time Limit: 2000/1000 MS (Java/Others) ...
 - [国嵌攻略][157][SPI总线介绍]
			
SPI总线架构 SPI(serial peripheral interface)串行外设接口,是一种高速,全双工,同步的通信总线.采用主从模式(master slave)架构,支持多个slave,一般 ...
 - ECharts插件的使用
			
ECharts插件:官网下载echarts.js开发者可以选择源码.下载地址:http://echarts.baidu.com/download.html 下载之后,echarts.js放在js文件夹 ...
 - Oracle_字段数据类型
			
Oracle_字段数据类型 数据库表字段的数据类型 字符数据类型 CHAR:存储固定长度的字符串 VARCHAR2 :存储可变长度的字符串 数值数据类型 NUMBER:存储整数和浮点数,格式为NUMB ...
 - 调用QQ聊天功能
			
[HTML]: <a href="javascript:void(0);" onclick="chatQQ()">咨询客服</a> fu ...