javaScript实现各排序算法总结

排序算法总结

冒泡排序

原理

顾名思义:冒泡排序每一轮将乱序数组中最大的数沉至数组尾部(或将最小的数上浮至数组头部)
冒泡排序需要两个嵌套的循环. 其中, 外层循环移动游标; 内层循环遍历游标及之后(或之前)的元素, 通过两两交换的方式, 每次只确保该内循环结束位置排序正确, 然后内层循环周期结束, 交由外层循环往后(或前)移动游标, 随即开始下一轮内层循环, 以此类推, 直至循环结束.

Tips: 由于冒泡排序只在相邻元素大小不符合要求时才调换他们的位置, 它并不改变相同元素之间的相对顺序, 因此它是稳定的排序算法.

JavaScript 代码实现
function bubbleSort(array){
    for(var i = 0; i < array.length - 1; i++){
        for(var j = 0; j < array.length - i - 1; j++){
            if(array[j] > array[j+1]){
                var temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
            }
        }
    }
    return array;
}


选择排序

原理

每轮在乱序数组中选择最大的数放至数组尾部(或选择最小的数放至数组头部)
两层循环.内层循环每执行一遍, 将选出本次待排序的元素中最小(或最大)的一个, 存放在数组的起始位置. 而 外层循环则确保扫描数组,保证每个元素执行内层操作

Tips: 选择排序每次交换的元素都有可能不是相邻的, 因此它有可能打破原来值为相同的元素之间的顺序. 比如数组[2,2,1,3], 正向排序时, 第一个数字2将与数字1交换, 那么两个数字2之间的顺序将和原来的顺序不一致, 虽然它们的值相同, 但它们相对的顺序却发生了变化. 我们将这种现象称作 不稳定性 .

JavaScript 代码实现
function selectSort(array){
    for(let i = 0; i < array.length; i++){
        let min = array[i];
        let minIndex = i;
        for(let j = i; j < array.length ; j++){
            if(array[j] < min){
                min = array[j];
                minIndex = j;
            }
        }
        swap(i, minIndex, array);
    }
    return array;
}


直接插入排序

原理

将待排序的元素按照大小顺序, 依次插入到一个已经排好序的数组之中, 直到所有的元素都插入进去.

Tips: 由于直接插入排序每次只移动一个元素的位置, 并不会改变值相同的元素之间的排序, 因此它是一种稳定排序.

function directInsertSort(array){
    var length = array.length, index, current;
    for(var i = 1; i < length; i++){
        index = i - 1;
        current = array[i];
        while(index >= 0 && array[index] > current){
            array[index + 1] = array[index];
            index--;
        }
        if(index + 1 != i){
            array[index + 1] = current;
        }
    }
    return array;
}

折半插入排序

原理

折半插入排序是直接插入排序的升级版. 鉴于插入排序第一部分为已排好序的数组, 我们不必按顺序依次寻找插入点, 只需比较它们的中间值与待插入元素的大小即可.

Tips: 同直接插入排序类似, 折半插入排序每次交换的是相邻的且值为不同的元素, 它并不会改变值相同的元素之间的顺序. 因此它是稳定的.

function halfInsertSort(array){
    for(var i = 1; i < array.length; i++) {
        var tail = i - 1;
        var head = 0;
        var current = array[i];
        while (head <= tail) {
            var half = (head + tail) >> 1;
            array[i] >= array[half] ? head = half + 1 : tail = half - 1;
        }
        for (var j = i; j > head; j--) {
            array[j] = array[j-1];
        }
        array[head] = current;
    }
    return array;
}

希尔排序

原理
  1. 将数组拆分为若干个子分组, 每个分组由相距一定”增量”的元素组成. 比方说将[0,1,2,3,4,5,6,7,8,9,10]的数组拆分为”增量”为5的分组, 那么子分组分别为 [0,5], [1,6], [2,7], [3,8], [4,9] 和 [5,10].
  2. 然后对每个子分组应用直接插入排序.
  3. 逐步减小”增量”, 重复步骤1,2.
  4. 直至”增量”为1, 这是最后一个排序, 此时的排序, 也就是对全数组进行直接插入排序

Tips: 我们知道, 单次直接插入排序是稳定的, 它不会改变相同元素之间的相对顺序, 但在多次不同的插入排序过程中, 相同的元素可能在各自的插入排序中移动, 可能导致相同元素相对顺序发生变化. 因此, 希尔排序并不稳定

function shellInsertSort(array, gap){
    gap = (gap == undefined) ? 1 : gap;
    var length = array.length, index, current;
    for(var i = gap; i < length; i++){
        index = i - gap;
        current = array[i];
        while(index >= 0 && array[index] > current){
            array[index + gap] = array[index];//组内使用插入排序
            index -= gap;
        }
        if(index + gap != i){
            array[index + gap] = current;
        }
    }
    return array;
}

function shellSort(array){
    var length = array.length, gap = length >> 1;
    while(gap > 0){
        shellInsertSort(array, gap);
        gap = gap >> 1;
    }
    return array;
}


归并排序

原理

将数组拆分为两个子数组, 分别排序, 最后才将两个子数组合并; 拆分的两个子数组, 再继续递归拆分为更小的子数组, 进而分别排序, 直到数组长度为1, 直接返回该数组为止.
(通常,开始组数为 数组长度/2,以保证每组元素不超过3个,之后组数依次递减,直至组数为1)

Tips: 归并排序严格按照从左往右(或从右往左)的顺序去合并子数组, 它并不会改变相同元素之间的相对顺序, 因此它也是一种稳定的排序算法.

function merge(left, right){
    var result = [];
    while(left.length && right.length){
        var item = left[0] <= right[0] ? left.shift() : right.shift();
        result.push(item);
    }
    return result.concat(left.length ? left : right);
}

function mergeSort(array){
    var length = array.length;
    if(length < 2){
        return array;
    }
    var m = (length >> 1),
        left = array.slice(0, m),
        right = array.slice(m);
    return merge(mergeSort(left), )
}


快速排序

原理

将数组拆分为两个子数组, 其中一个子数组的所有元素都比另一个子数组的元素小, 然后对这两个子数组再重复进行上述操作, 直到数组不可拆分, 排序完成.

Tips: 同选择排序相似, 快速排序每次交换的元素都有可能不是相邻的, 因此它有可能打破原来值为相同的元素之间的顺序. 因此, 快速排序并不稳定.

function quickSort(array){
    if(!array || Object.prototype.toString.call(array).toLowerCase().indexOf('array') < 0){
        throw new Error('quickSort(): First arguments must be an Array.')
    }
    if(array.length <= 1){
        return array;
    }

    var length = array.length,
        pivotIndex = Math.floor(array.length / 2),
        pivot = array.splice(pivotIndex, 1)[0],
        left = [],
        right = [],
        i = 0;

    length -= 1;

    for(; i < length; i++) {
        if (array[i] < pivot) {
            left.push(array[i]);
        } else {
            right.push(array[i]);
        }
    }

    return quickSort(left).concat([pivot], quickSort(right));
}


堆排序

预备知识

堆:可以将堆看作完全二叉树, k(i) 相当于是二叉树的非叶子节点, k(2i) 则是左子节点, k(2i+1)是右子节点.
大根堆:要求每个子节点的值都不大于其父节点的值, 即array[childIndex] <= array[parentIndex], 最大的值一定在堆顶.
小根堆:每个子节点的值都不小于其父节点的值, 最小的值一定在堆顶

原理

算法的基本思想(以大根堆为例):
1. 先将初始序列K[1..n]建成一个大根堆, 此堆为初始的无序区.
2. 再将关键字最大的记录K1 (即堆顶)和无序区的最后一个记录K[n]交换, 由此得到新的无序区K[1..n-1]和有序区K[n], 且满足K[1..n-1].keys≤K[n].key
3. 交换K1 和 K[n] 后, 堆顶可能违反堆性质, 因此需将K[1..n-1]调整为堆. 然后重复步骤2, 直到无序区只有一个元素时停止.

Tips: 由于堆排序中初始化堆的过程比较次数较多, 因此它不太适用于小序列. 同时由于多次任意下标相互交换位置, 相同元素之间原本相对的顺序被破坏了, 因此, 它是不稳定的排序.

function make_heap(array, x, length){
    var lchild = 2 * x + 1,
        rchild = 2 * x + 2,
        largest = x,
        temp;
    if(lchild < length && array[lchild] > array[largest]){
        largest = lchild;
    }
    if(rchild < length && array[rchild] > array[largest]){
        largest = rchild;
    }
    if(largest != x){
        temp = array[x];
        array[x] = array[largest];
        array[largest] = temp;
        make_heap(array, largest, length);
    }
}

function heapSort(array) {
    var heapSize = array.length, temp;

    //建堆
    for(var i = Math.floor(heapSize / 2) - 1; i >= 0; i--){
        make_heap(array, i, heapSize);
    }

    //堆排序
    for(var j = heapSize - 1; j >= 1; j--){
        temp = array[0];
        array[0] = array[j];
        array[j] = temp;
        console.log(array);
        make_heap(array, 0, --heapSize);
    }
    return array;
}


总结:排序算法比较

排序算法平均时间复杂度最好情况最坏情况空间复杂度稳定性
冒泡排序O(n²)O(n)O(n²)O(1)稳定
选择排序O(n²)O(n²)O(n²)O(1)不稳定
直接插入排序O(n²)O(n)O(n²)O(1)稳定
折半插入排序O(n²)O(n)O(n²)O(1)稳定
希尔排序O(n^1.3)O(nlogn)O(n²)O(1)不稳定
归并排序O(nlog₂n)O(nlog₂n)O(nlog₂n)O(1)稳定
快速排序O(nlog₂n)O(nlog₂n)O(n²)O(nlog₂n)不稳定
堆排序O(nlog₂n)O(nlog₂n)O(nlog₂n)O(1)不稳定
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值