排序-JS

155 阅读3分钟

吼 背景是有个同学面试京东被问了说各种排序,一想想自己也是背了那么久的,然后又记不得,没有系统的整理过;

评价术语:

  1. 时空复杂度;执行耗时和运行所需的内存空间;
  2. 稳定:在排序中,如果a=b,稳定则是指这两的index相对位置不改变;记住(快排和堆,选择排序都不稳定!)

有一张图用了很久的图了 反正我看了无数遍也记不住

image.png

一般排序会分为插入(直接插入,希尔)、选择(简单选择,堆)、交换(冒泡,快排)、归并和基数

插入排序:

直接插入排序

很容易理解啊,打扑克排序哈哈;看代码 动画看参考文献1

function insertSort(arr){
    for(let i=0;i<arr.length;i++){
        var key = arr[i];
        var j = i - 1;
        while(j>=0 && arr[j]>key){
            arr[j+1] = arr[j];
            j--;
        }
        arr[j+1]=key
    }
    return arr
}

如果改进的话,可以把和前面已排序的插入过程变成二分;
时间复杂度:平均(n2) 最好(n) 最差(n2) 稳定

希尔:

可以突破n2时间复杂度的算法,改进了插入的过程,通过设置间隔序列实现;举个例子:也就是第一次1和5比,2和6比,3和7比;然后完了把增量减半;继续;一般不考

代码wip:

//希尔排序,自组采用直接插入排序 针对有序序列在插入时采用交换法
function shellSort(arr){
    //逐步降低步长直至为1为止
    for(let shellWidth = arr.length/2;shellWidth>0;shellWidth/2){
        //根据步长,将数组进行分组,并使用插入排序法进行交换排序
        //从增量大小的那组数据进行插入排序
        for(let atom =shellWidth; atom<arr.length; atom++ ){
            //atom-shellWidth  表示和该元素同组的隔壁相邻的元素,对于同一组的元素,进行插入排序
            while(atom-shellWidth>0&&arr[atom-shellWidth]>arr[atom]){
                swap(arr,atom-shellWidth,atom);
                atom=atom-shellWidth;
            }
        }
    }        
}

选择排序:

简单选择排序

找到数组中最小的元素,放到素组最左边;需要n-1趟排序

function chooseSort(arr){
    for(let i=0; i<arr.length; i++){
        minIndex = i;
        for(let j=i+1; j<arr.length; j++){
            if(arr[j]<arr[minIndex]){
                minIndex = j
            }
        }
        [arr[i],arr[minIndex]] = [arr[minIndex],arr[i]] 
    }
    console.log(arr)
}

时间复杂度,平均最好最坏都是n2,不稳定

堆排序

大顶堆就是根节点大于左右子节点,那么先把数组构建一个大顶堆,然后把根节点和最后一个进行交换,到此最大值就在最后了;然后n-1继续构造;以此类推;

这里边有几个数学知识 背一下吧: 第一个叶子节点下标:Math.floor(arr.length/2 - 1)

参考:www.jianshu.com/p/90bf2dcd6…

nlogn 不稳定

交换排序:(重点考点)

冒泡:

image.png

示意图:cuijiahua.com/blog/2017/1…

对比

function bubbleSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {        //相邻元素两两对比
                var temp = arr[j+1];        //元素交换
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

n2最坏和平均 最好的时候是inplace的时候n;是稳定的排序;

可以通过一个flag来进行改进;如果进行某一趟排序时并没有进行数据交换,则说明所有数据已经有序,可立即结束排序,避免不必要的比较过程。

改进之后的代码:

function bubbleSort(arr){
    for(let i = 0; i < arr.length; i++) {
    let flag = true
    for(let j = 0; j < arr.length - i - 1; j++) {
        if(arr[j] > arr[j+1]) {
          flag = false
          let temp = arr[j]
          arr[j] = arr[j+1]
          arr[j+1] = temp
        }
      }
      // 这个flag的含义是:如果`某次循环`中没有交换过元素,那么意味着排序已经完成
      if(flag)break;
    }
    return arr
  }

快排:

nlogn! 不稳定! 有nlogn的空间复杂度

  1. 选取基准元素
  2. 比基准元素小的元素放到左边,大的放右边
  3. 在左右子数组中重复步骤一二,直到数组只剩下一个元素
  4. 向上逐级合并数组

image.png

优化:开了俩内存,不太行哦;

  function quickSort(arr, left, right) {          //这个left和right代表分区后“新数组”的区间下标,因为这里没有新开数组,所以需要left/right来确认新数组的位置
    if (left < right) {
        let pos = left - 1                      //pos即“被置换的位置”,第一趟为-1
        for(let i = left; i <= right; i++) {    //循环遍历数组,置换元素
            let pivot = arr[right]              //选取数组最后一位作为基准数,
            if(arr[i] <= pivot) {               //若小于等于基准数,pos++,并置换元素, 这里使用小于等于而不是小于, 其实是为了避免因为重复数据而进入死循环
                pos++
                let temp = arr[pos]
                arr[pos] = arr[i]
                arr[i] = temp
            }
        }
        //一趟排序完成后,pos位置即基准数的位置,以pos的位置分割数组
        quickSort(arr, left, pos - 1)        
        quickSort(arr, pos + 1, right)
    }
    return arr      //数组只包含1或0个元素时(即left>=right),递归终止
}

image.png

归并

image.png 参考:
[1] juejin.cn/post/684490… [2]juejin.cn/post/684490…