专栏名称: 程序员大咖
为程序员提供最优质的博文、最精彩的讨论、最实用的开发资源;提供最新最全的编程学习资料:PHP、Objective-C、Java、Swift、C/C++函数库、.NET Framework类库、J2SE API等等。并不定期奉送各种福利。
目录
相关文章推荐
程序员小灰  ·  如何用DeepSeek来变现?90%的人都不知道 ·  昨天  
码农翻身  ·  强烈建议尽快搞个软考证!(2025重大红利) ·  4 天前  
程序员小灰  ·  不要脸!DeepSeek被美国海军禁用 ·  6 天前  
程序员的那些事  ·  它们急了!DeepSeek 遭 ... ·  5 天前  
OSC开源社区  ·  🐍OSCHINA恭祝大家蛇年除夕快乐​🧧 ·  1 周前  
51好读  ›  专栏  ›  程序员大咖

JavaScript排序,不只是冒泡

程序员大咖  · 公众号  · 程序员  · 2017-03-25 19:44

正文

我  相  信  这  么  好 看  的  你 

已  经  置 顶  了  我

非常非常推荐大家去读一本 gitBook 上的书 - 十大经典排序算法 : 

https://sort.hust.cc/ , 本文的动图和演示代码均是这里面的。


做编程,排序是个必然的需求。前端也不例外,虽然不多,但是你肯定会遇到。

不过说到排序,最容易想到的就是冒泡排序,选择排序,插入排序了。


冒泡排序

依次比较相邻的两个元素,如果后一个小于前一个,则交换,这样从头到尾一次,就将最大的放到了末尾。


从头到尾再来一次,由于每进行一轮,最后的都已经是最大的了,因此后一轮需要比较次数可以比上一次少一个。虽然你还是可以让他从头到尾来比较,但是后面的比较是没有意义的无用功,为了效率,你应该对代码进行优化。


图片演示如下:



代码实现:


  1. function bubbleSort(arr) {

  2.    var len = arr.length;

  3.    for (var i = 0; i  len - 1; i++) {

  4.        for (var j = 0; j  len - 1 - i; j++) {

  5.            if (arr[j] > arr[j+1]) {        // 相邻元素两两对比

  6.                var temp = arr[j+1];        // 元素交换

  7.                arr[j+1] = arr[j];

  8.                arr[j] = temp;

  9.            }

  10.        }

  11.    }

  12.    return arr;

  13. }


选择排序

选择排序我觉得是最简单的了,大一学 VB 的时候,就只记住了这个排序方法,原理非常简单:每次都找一个最大或者最小的排在开始即可。

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置

  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

  3. 重复第二步,直到所有元素均排序完毕。


动图演示:



代码演示:


  1. function selectionSort(arr) {

  2.    var len = arr.length;

  3.    var minIndex, temp;

  4.    for (var i = 0; i  len - 1; i++) {

  5.        minIndex = i;

  6.        for (var j = i + 1; j  len; j++) {

  7.            if (arr[j]  arr[minIndex]) {     // 寻找最小的数

  8.                minIndex = j;                 // 将最小数的索引保存

  9.            }

  10.        }

  11.        temp = arr[i];

  12.        arr[i] = arr[minIndex];

  13.        arr[minIndex] = temp;

  14.    }

  15.    return arr;

  16. }


插入排序

插入排序也比较简单。就像打扑克一样,依次将拿到的元素插入到正确的位置即可。


  1. 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)


动图演示:



代码示例:


  1. function insertionSort(arr) {

  2.    var len = arr.length;

  3.    var preIndex, current;

  4.    for (var i = 1; i  len; i++) {

  5.        preIndex = i - 1;

  6.        current = arr[i];

  7.        while(preIndex >= 0 && arr[preIndex] > current) {

  8.            arr[preIndex+1] = arr[preIndex];

  9.            preIndex--;

  10.        }

  11.        arr[preIndex+1] = current;

  12.    }

  13.    return arr;

  14. }


简单的代价是低效


上面三种都是非常简单的排序方法,简单的同时呢,效率也会比较低,还是拿这本书里的对比图来说明:



时间复杂度都高达 O(n^2) ,而它们后面的一些排序算法时间复杂度基本都只有 O(n log n) 。


我的强迫症又犯了,我想要高效率一点的排序方法。


归并排序

简单把这本书的内容过了一遍,当时就理解了这个归并排序,因此这里就谈一下这个归并排序吧。


基本原理是分治法,就是分开并且递归来排序。


步骤如下:


  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;

  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

  4. 重复步骤 3 直到某一指针达到序列尾;

  5. 将另一序列剩下的所有元素直接复制到合并序列尾。


动图演示:



代码示例:


  1. function mergeSort(arr) {  // 采用自上而下的递归方法

  2.    var len = arr.length;

  3.    if(len  2) {

  4.        return arr;

  5.    }

  6.    var middle = Math.floor(len / 2),

  7.        left = arr.slice(0, middle),

  8.        right = arr.slice(middle);

  9.    return merge(mergeSort(left), mergeSort(right));

  10. }


  11. function merge(left, right)

  12. {

  13.    var result = [];


  14.    while (left.length && right.length) {

  15.        if (left[0]  right[0]) {

  16.            result.push(left.shift());

  17.        } else {

  18.            result.push(right.shift());

  19.        }

  20.    }


  21.    while (left.length)

  22.        result.push(left.shift());


  23.    while (right.length)

  24.        result.push(right.shift());


  25.    return result;

  26. }


既然是个爱折腾的人,折腾了总得看看效果吧。


效率测试

由于我学这个来进行排序不是对简单数组,数组内都是对象,要对对象的某个属性进行排序,还要考虑升降序。


因此我的代码实现如下:


  1. /**

  2. * [归并排序]

  3. * @param  {[Array]} arr   [要排序的数组]

  4. * @param  {[String]} prop  [排序字段,用于数组成员是对象时,按照其某个属性进行排序,简单数组直接排序忽略此参数]

  5. * @param  {[String]} order [排序方式 省略或asc为升序 否则降序]

  6. * @return {[Array]}       [排序后数组,新数组,并非在原数组上的修改]

  7. */

  8. var mergeSort = (function() {

  9.    // 合并

  10.    var _merge = function(left, right, prop) {

  11.        var result = [];


  12.        // 对数组内成员的某个属性排序

  13.        if (prop) {

  14.            while (left.length && right.length) {

  15.                if (left[0][prop]  right[0][prop]) {

  16.                    result.push(left.shift());

  17.                } else {

  18.                    result.push(right.shift());

  19.                }

  20.            }

  21.        } else {

  22.            // 数组成员直接排序

  23.            while (left.length && right.length) {

  24.                if (left[0]  right[0]) {

  25.                    result.push(left.shift());

  26.                } else {

  27.                    result.push(right.shift());

  28.                }

  29.            }

  30.        }


  31.        while (left.length)

  32.            result.push(left.shift());


  33.        while (right.length)

  34.            result.push(right.shift());


  35.        return result;

  36.    };


  37.    var _mergeSort = function(arr, prop) { // 采用自上而下的递归方法

  38.        var len = arr.length;

  39.        if (len  2) {

  40.            return arr;

  41.        }

  42.        var middle = Math.floor(len / 2),

  43.            left = arr.slice(0, middle),

  44.            right = arr.slice(middle);

  45.        return _merge(_mergeSort(left, prop), _mergeSort(right, prop), prop);

  46.    };


  47.    return function(arr, prop, order) {

  48.        var result = _mergeSort(arr, prop);

  49.        if (!order || order.toLowerCase() === 'asc') {

  50.            // 升序

  51.            return result;

  52.        } else {

  53.            // 降序

  54.            var _ = [];

  55.            result.forEach(function(item) {

  56.                _.unshift(item);

  57.            });

  58.            return _;

  59.        }

  60.    };

  61. })();


需要对哪个属性进行排序是不确定,可以随意指定,因此写成了参数。有由于不想让这些东西在每次循环都进行判断,因此代码有点冗余。


关于降序的问题,也没有加入参数中,而是简单的升序后再逆序输出。原因是不想让每次循环递归里都去判断条件,所以简单处理了。


下面就是见证效率的时候了,一段数据模拟:



  1. var getData = function() {

  2.    return Mock.mock({

  3.        "list|1000": [{

  4.            name: '@cname',

  5.            age: '@integer(0,500)'

  6.        }]

  7.    }).list;

  8. };


上面使用 Mock 进行了模拟数据,关于Mock : http://mockjs.com/

实际测试来啦:


  1. // 效率测试

  2. var arr = getData();


  3. console.time('归并排序');

  4. mergeSort(arr, 'age');

  5. console.timeEnd('归并排序');


  6. console.time('冒泡排序');

  7. for (var i = 0, l = arr.length; i  l - 1; ++i) {

  8.    var temp;

  9.    for (var j = 0; j  l - i - 1; ++j) {

  10.        if (arr[j].age > arr[+ 1].age) {

  11.            temp = arr[+ 1];

  12.            arr[+ 1] = arr[j];

  13.            arr[j] = temp;

  14.        }

  15.    }

  16. }

  17. console.timeEnd('冒泡排序');


进行了五次,效果如下:


  1. // 归并排序: 6.592ms

  2. // 冒泡排序: 25.959ms


  3. // 归并排序: 1.334ms

  4. // 冒泡排序: 20.078ms


  5. // 归并排序: 1.085ms

  6. // 冒泡排序: 16.420ms


  7. // 归并排序: 1.200ms

  8. // 冒泡排序: 16.574ms


  9. // 归并排序: 2.593ms

  10. // 冒泡排序: 12.653ms


最低4倍,最高近16倍的效率之差还是比较满意的。


虽然 1000 条数据让前端排序的可能性不大,但是几十上百条的情况还是有的。

另外由于 node, JavaScript 也能运行的服务端了,这个效率的提升也还是有用武之地的。


一点疑问

归并排序里面使用了递归,在《数据结构与算法 JavaScript 描述》中,作者给出了自下而上的迭代方法。但是对于递归法,作者却认为:


However, it is not possible to do so in JavaScript, as the recursion goes too deep for the language to handle.


然而,在 JavaScript 中这种方式不太可行,因为这个算法的递归深度对它来讲太深了。


gitbook 上这本书的作者对此有疑问,我也有疑问。


归并中虽然用了递归,但是他是放在 return 后的呀。关于在 renturn 后的递归是有尾递归优化的呀。


关于尾递归优化是指:本来外层函数内部再调用一个函数的话,由于外层函数需要等待内层函数返回后才能返回结果,进入内层函数后,外层函数的信息,内存中是必须记住的,也就是调用堆栈。而内部函数放在 return 关键字后,就表示外层函数到此也就结束了,进入内层函数后,没有必要再记住外层函数内的所有信息。


上面是我的理解的描述,不知道算不算准确。chrome 下已经可以开启尾递归优化的功能了,我觉得这个递归是不该影响他在 JavaScript 下的使用的。


最后

有兴趣的话,推荐读读这本书,进行排序的时候,可以考虑一些更高效的方法。

不过需要注意的是,这些高效率的排序方法,一般都需要相对较多的额外内存空间,需要权衡一下。


另外,非常小规模的数据就没有必要了。一是影响太小,而是我们人的效率问题,一分钟能从头写个冒泡、选择、插入的排序方法,而换成是归并排序呢?


作者:依韵

链接:

http://blog.cdswyda.com/post/javascript/2017-03-22-js-sort-not-only-bubblesort?utm_source=tuicool&utm_medium=referral