首页 > 代码库 > JavaScript算法-排序算法

JavaScript算法-排序算法

? 此生之路,我将走过;走过这一次,便再也无法重来。所有力所能及的善行,所有充盈于心的善意,我将毫不吝惜,即刻倾予。我将再不拖延,再不淡漠,只因此生之路,再也无法重来。

对计算机中存储的数据执行的两种最常见操作是排序和索引。下述阐述的排序方式,暂且都是用数组进行测试(从小到大)。

var dataAry = [5, 4, 3, 7, 1, 2, 8, 6, 9]; // 测试数组
/**
 *【工具方法】交换数组中两个值
 * @param ary 数组
 * @param i 下标i
 * @param j 下标j
 */
function swap(ary, i, j) {
    var temp = ary[i];
    ary[i] = ary[j];
    ary[j] = temp;
}

冒泡排序

? 之所以称为冒泡排序是因为使用这种排序算法时,数据值会像气泡一样从数组的一端漂浮到另一端。如,从小到大排序:其会比较相邻的数据,当左侧值大于右侧值时将它们进行交换。

冒泡排序算法的运作如下:(从小到大)

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
function bubbleSort(dataAry) {
    var len = dataAry.length;
    for(var i = 0; i < len; i++) {
        for(var j = 0; j < len - i; j++) {
            if(dataAry[j] > dataAry[j+1]) {
                swap(dataAry, j, j+1);
            }
        }
    }
    return dataAry;
}
// 测试
console.log(bubbleSort(dataAry)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

选择排序

? 从数组的第一个数据开始,将第一个数据和其他数据进行比较。它的工作原理是每一次从待排序的数据中选出最小(或最大)的一个数据,存放在序列的起始位置,直到全部待排序的数据元素排完。

function selectionSort(dataAry) {
    var len = dataAry.length;
    for(var i = 0; i < len - 1; i++) {
        for(var j = i + 1; j < len; j++) {
            if(dataAry[i] > dataAry[j]) {
                swap(dataAry, i , j);
            }
        }
    }
    return dataAry;
}
// 测试
console.log(selectionSort(dataAry)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

插入排序

? 插入排序有两个循环,外循环将数组元素挨个移动,而内循环则对外循环中选定的元素及它后面的那个元素比较。如果外循环中选中元素小,那么数组元素会向右移动,为内循环中的这个元素腾出位置。每步将一个待排序的纪录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。

直接插入排序算法的说明<有1-9张标有是数字的卡片>:
1. 取出第一张卡片直接放到桌子最左侧
2. 取出第二张卡片,如果该卡片标注的数字小于第一张,则将第一张卡片向右移动一格;否则放到右侧;
3. 取出第n张卡片,将该卡片与第n-1、n-2···第1张对比,小于等于则右移继续对比,大于则停止对比将该值插入对应位置。

function insertionSort(dataAry) {
    var temp, inner;
    // 外循环(跳过第一个值,因为第一个直接放到最左侧)
    for(var outer = 1, len = dataAry.length; outer < len; outer++) {
        temp = dataAry[outer];
        inner = outer;
        // 内循环,比左侧值小就移动让位
        while(inner > 0 && (dataAry[inner - 1] >= temp)) {
            dataAry[inner] = dataAry[inner - 1];
            inner--;
        }
        dataAry[inner] = temp;
    }
    return dataAry
}
// 测试
console.log(insertionSort(dataAry)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

希尔排序

? 该算法在插入排序的基础上做了相应改善。其需要预先(或动态)定义一个间隔序列来表示在排序过程中进行比较的元素之间的间隔。对被间隔的每组元素使用直接插入排序算法排序;随着间隔序列中的数逐渐减小,每组包含的元素越来越多,当间隔减至1时,整个文件恰被分成一组,算法便终止。这确保了在开始最后一次处理时,大部分元素都已在正确位置,必须再进行多次数据交换,这就是希尔排序比插入排序更高效的地方。

希尔排序算法说明:
1. 先取一个小于n的整数d1作为第一个间隔,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;
2. 取第二个间隔值d2<d1重复上述的分组和排序;
3. 直至所取的间隔为1,即所有记录放在同一组中进行直接插入排序为止。

技术分享

/**
 * 希尔排序
 * @param dataAry 数据数组
 * @param gaps    间隔数组
 */
function shellSort(dataAry, gaps) {
    var temp, inner, currentGap;
    // 遍历间隔
    for(var g = 0, gLen = gaps.length; g < gLen; g++) {
        // 当前间隔
        currentGap = gaps[g];
        // 直接插入法外循环
        for(var outer = currentGap, len = dataAry.length; outer < len; outer++) {
           temp = dataAry[outer];
           inner = outer;
           // 直接插入法内循环(注意对比间隔值)
           while(inner >= currentGap && (dataAry[inner - currentGap] >= temp)) {
               dataAry[inner] = dataAry[inner - currentGap];
               inner = inner - currentGap;
           }
           dataAry[inner] = temp;
        }
    }
    return dataAry;
}
// 测试
console.log(shellSort(dataAry, [3, 2, 1])); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

归并排序

把一系列排好序的子序列合并成一个大的完整有序序列。归并排序通常使用递归来实现。

自顶向下的归并排序(递归)

技术分享

function mergeSort(dataAry) {
    if(dataAry.length === 1) {
        return dataAry;
    }
    var mid = Math.floor(dataAry.length/2);
    var leftAry = dataAry.slice(0, mid);
    var rightAry = dataAry.slice(mid);
    return merge(mergeSort(leftAry), mergeSort(rightAry));
}
/**
 * 合并数组
 * @param dataAry
 */
function merge(leftAry, rightAry) {
    var result = [];
    while(leftAry.length > 0 && rightAry.length > 0) {
        if(leftAry[0] < rightAry[0]) {
            result.push(leftAry.shift());
        }else {
            result.push(rightAry.shift());
        }
    }
    return result.concat(leftAry).concat(rightAry);
}
// 测试
console.log(mergeSort(dataAry)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

? 上述mergeSort()被执行了2n-1次(上述被执行了17次),一旦数组元素个数增多,采用递归的方式将不再可取,元素个数超过1500在Firfox下就会造成栈溢出!

自底向上的归并排序(迭代)

? 首先将数据集分解为一组只有一个元素的数组,然后通过创建一组左右数组将其合并,每次合并确保数据已排好序,直到最后所有数据排序完成。

技术分享

function mergeSort(dataAry) {
    if(dataAry.length < 2) {
        return dataAry;
    }
    var step = 1; // 控制子序列大小
    var left, right; // 左、右下标
    while(step < dataAry.length) {
        left = 0;
        right = step;

        while((right + step) <= dataAry.length) {
            mergeArrays(dataAry, left, left+step, right, right+step);
            left = right + step;
            right = left + step;
        }
        // 不能被分组的元素
        if(right < dataAry.length) {
            mergeArrays(dataAry, left, left+step, right, dataAry.length);
        }
        step = step * 2;
    }
    return dataAry;
}

/**
 * 合并数组<包含头,不包含尾>
 * @param ary
 * @param startLeft 
 * @param endLeft
 * @param startRight
 * @param endRight
 */
function mergeArrays(ary, startLeft, endLeft, startRight, endRight) {
    var leftAry = new Array(endLeft - startLeft + 1),
        rightAry = new Array(endRight - startRight + 1);

    var k = startLeft;
    for(var i = 0; i < (leftAry.length - 1); i++) {
        leftAry[i] = ary[k];
        k++;
    }
    leftAry[leftAry.length - 1] = Infinity; // 哨兵值

    k = startRight;
    for(var j = 0; j < (rightAry.length - 1); j++) {
        rightAry[j] = ary[k];
        k++;
    }
    rightAry[rightAry.length - 1] = Infinity; // 哨兵值

    // 对比左右元素大小
    var m = 0, n = 0;
    for(var k = startLeft; k < endRight; k++) {
        if(leftAry[m] <= rightAry[n]) {
            ary[k] = leftAry[m];
            m++;
        }else {
            ary[k] = rightAry[n];
            n++;
        }
    }
}
// 测试
console.log(quickSort(dataAry)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

快速排序

? 快速排序是处理大数据集最快的排序算法之一。其核心思想为“分而治之”。设立基值,通过递归的方式将数据一次分解为包含较小元素和较大元素的不同子序列,然后不断重复,直到所有数据变为有序。

快速排序算法说明:
1. 选择一个基准元素,将列表分隔为两个子序列;
2. 将所有小于基准元素的数据放在基准值的左侧,大于基准值元素的数据放在基准值右侧;
3. 分别对较小元素的子序列和较大元素的子序列重复步骤1和2。

快速排序<方式一>:从左侧起开始循环数组与基值进行对比

function quickSort(dataAry) {
    if(dataAry.length === 0) {
        return [];
    }
    var left = [], right = [];
    var pivot = dataAry[0];
    for(var i = 1; i < dataAry.length; i++) {
        dataAry[i] < pivot ? left.push(dataAry[i]) : right.push(dataAry[i]);
    }
    return quickSort(left).concat(pivot, quickSort(right));
}
// 测试
console.log(quickSort(dataAry)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

快速排序<方式二>:从左、右两侧一起移动,与基值进行对比

技术分享

function quickSort(arr, left, right) {
    if (left > right) return;
    var temp,
        i = left,
        j = right;
    // 设基准值
    var pivot = arr[left];
    while (i != j) {
        // 先从右侧开始(找到小于基值的第一个数据)
        while (i < j && arr[j] >= pivot) {
            j--;
        }
        // 然后从左侧开始(找到大于基值的第一个数据)
        while (i < j && arr[i] <= pivot) {
            i++;
        }
        // 交换数据上述两个元素,继续查找,直到i和j相遇
        if (i < j) {
            temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    // 将基值与相遇数据进行交换<确保了基值位于元素中央位置>
    temp = arr[i];
    arr[i] = arr[left];
    arr[left] = temp;
    // 基值左侧数组按上述方式递归执行
    quickSort(arr, left, i - 1);
    // 基值右侧数组按上述方式递归执行
    quickSort(arr, i + 1, right);
    return arr;
}
// 测试
console.log(quickSort(dataAry, 0, dataAry.length - 1)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

总结

冒泡排序、选择排序、插入排序为基本排序算法,希尔排序、归并排序(迭代)、快速排序为高级排序算法

排序算法 100条所耗时间 10000条所耗时间 100000条所耗时间
冒泡排序 16毫秒 584毫秒 54619毫秒
选择排序 <1毫秒 183毫秒 18175毫秒
插入排序 <1毫秒 27毫秒 2660毫秒
希尔排序 <1毫秒 13毫秒 1384毫秒
归并排序(迭代) <1毫秒 6毫秒 40毫秒
快速排序(方式一) <1毫秒 17毫秒 98毫秒
快速排序(方式二) <1毫秒 3毫秒 13毫秒
<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    JavaScript算法-排序算法