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实现如下:

//冒泡排序
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)

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

实现步骤如下:

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

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

实现步骤如下:

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

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

归并排序
归并排序

具体的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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 数据结构与算法--排序之冒泡、选择、插入、希尔 我们关注的主要对象是重新排列数组元素的算法,每个元素都有一个主键,...
    sunhaiyu阅读 1,118评论 2 12
  • 排序的基本概念 在计算机程序开发过程中,经常需要一组数据元素(或记录)按某个关键字进行排序,排序完成的序列可用于快...
    Jack921阅读 1,416评论 1 4
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,159评论 0 52
  • 回顾一下自己写的文字,居然不知不觉坚持到了30天,强迫自己写了近30个概念的理解,难免又生出感慨,那些年我们听了很...
    问天eric阅读 336评论 0 0
  • 叶语风蹙簌簌难堪,达旦亦凄寒 木吟云似炭,酒清茶淡,帘散花残 仙殿笙歌常叹,人间偶栖安 天姥连天看,沟壑弯弯 长剑...
    水手张叁阅读 363评论 0 2