基本时间复杂度为O(N*logN)的排序算法

归并排序

利用master公式可求得时间复杂度为O(N*logN),额外空间复杂度为O(N)
master公式的具体使用可以去看往期的文章。

/**归并排序 
     * master:T(N) = 2 * (N/2) + O(N^1)
     * log(2,2) = 1
     * 所以时间复杂度为O(N^logN)
    */
    process1(arr:number[],l:number,r:number,order:Function){
        if(l == r) return;
        let mid = l + ((r-l)>>1);  //防溢出的求中点方式,万一数组很大的情况下
        this.process1(arr,l,mid,order);
        this.process1(arr,mid + 1,r,order);
        this.merge(arr,l,mid,r,order)
    }

    merge(arr:number[],l:number,m:number,r:number,order:Function){
        let help:number[] = [];
        let p1 = l;
        let p2 = m + 1;
        while(p1 <= m && p2 <= r){
            if(order(arr[p1],arr[p2])){
                help.push(arr[p1++]);
            }else{
                help.push(arr[p2++]);
            }
        }
        while(p1 <= m){
            help.push(arr[p1++]);
        }
        while(p2 <= r){
            help.push(arr[p2++])
        }
        for(let i = 0; i<help.length;i++){
            arr[l+i] = help[i];
        }
    }

拓展:求数组的中所有数的小和(小和指左边所有比自己小的数之和)
方法:使用归排升序求小和

    /**
     * 逆向思维:右边有几个数比自己大,就加多少个自己
     * 使用归排升序求小和,实现O(N^logN)的时间复杂度
     */
    smallSum(arr:number[],l:number,r:number):number{
        if(l == r) return 0;
        let mid = l + ((r-l)>>1);
        let sum1 = this.smallSum(arr,l,mid);
        let sum2 = this.smallSum(arr,mid+1,r);
        return this.mergeAndS(arr,l,mid,r,sum1 + sum2)
    }

    mergeAndS(arr:number[],l:number,m:number,r:number,s:number):number{
        let help:number[] = [];
        let p1 = l;
        let p2 = m + 1;
        while(p1 <= m && p2 <= r){
            if(arr[p1] < arr[p2]){
                s+=arr[p1] * (r-p2 + 1);
                help.push(arr[p1++]);
            }else{
                help.push(arr[p2++]);
            }
        }

        while(p1 <= m){
            help.push(arr[p1++]);
        }

        while(p2 <= r){
            help.push(arr[p2++])
        }

        for(let i = 0;i<help.length;i++){
            arr[l+i] = help[i];
        }

        return s;
    }

快速排序

快速排序3.0,即每次选择数组中任意的一个数放至队尾,作为划分对象num值进行partition。
时间复杂度,最好的情况为O(NlogN),最坏情况为O(N^2),最后用长期期望算得O(NlogN)
空间复杂度 O(logN),即最坏情况的二叉树高度

  /*
     *快速排序3.0,即每次选择数组中任意的一个数放至队尾,作为划分对象num值进行partition。
     * 时间复杂度,最好的情况为O(N*logN),最坏情况为O(N^2),最后用长期期望算得O(N*logN)
     * 空间复杂度为O(logN),即最坏情况的二叉树高度
     */
    quickSort(arr:number[],l:number,r:number){
        if(l < r){
            let ran1 = Math.floor(Math.random() * (r - l + 1));
            let ran = l + ran1;
            this.swap(arr,ran,r);
            let part = this.partition(arr,l,r,arr[r]);
            this.quickSort(arr,l,part[0]);
            this.quickSort(arr,part[1],r);        
        }
    }
    
    /**
     * 荷兰国旗问题(区域划分),设定num值,数组中小于num值的都放在左边,等于的放中间,大于放右边
     * 单独执行的空间复杂度O(1),时间复杂度O(n)
     */
      partition(arr:number[],l:number,r:number,num:number){
        let small: number = l - 1;
        let big: number = r + 1;
        while (l != big) { //当指针p没有触碰大于区域时则继续
            if (arr[l] < num) {
                this.swap(arr, l++, ++small)
            } else if (arr[l] == num) {
                l++;
            } else {
                this.swap(arr, l, --big);
            }
        }
        return [small,big];
    }

堆排序

时间复杂度O(N*logN),空间复杂度O(1)

    /**
     *   0
     *  / \
     *  1  2
     * / \
     * 3 4
     *(小根堆的定义,分别对应数组中的下标)
     /

     /**
      *    5(0)
      *  /     \
      *  4(1)  3(2)
      * /   \
      * 2(3) 1(4)
      * (大根堆的定义,括号内分别对应数组中的下标)
      */



    /**
     * 以小根堆为例,当数据在现在的位置上比父节点小时,与父节点交换位置,直至比父节点大或已处于根节点
     * @index 数据当前所在的数组下标位置
    */
    heapInsert(arr: number[], index: number, comparator: Function) {
        while (index > 0 && comparator(arr[index], arr[Math.floor((index - 1) / 2)])) {
            this.swap(arr, Math.floor((index - 1) / 2), index);
            index = Math.floor((index - 1) / 2);
        }
    }

    /**
     * 以小根堆为例,当数据在现在的位置上比左右子节点中任意一个大时,与该子节点交换位置,直至比左右子节点都小或没有左右子节点
     * @heapSize 堆的大小
     * @index 数据当前所在的数组下标位置
     */
    heapify(arr: number[], index: number, heapSize: number, comparator: Function) {
        while ((index * 2 + 1) < heapSize) {
            let miner = comparator(arr[index * 2 + 1], arr[index * 2 + 2]) || index * 2 + 2 >= heapSize ? index * 2 + 1 : index * 2 + 2;
            if (comparator(arr[index], arr[miner])) break;
            else {
                this.swap(arr, index, miner);
                index = miner;
            }
        }
    }

    /**
     * 堆排序,将数组数据放入堆结构中,利用堆结构进行排序
     * 升序用大根堆,降序用小根堆
     * 时间复杂度为O(NlogN),空间复杂度为O(1)
     */
    heapSort(arr: number[], comparator: Function) {
        if (arr.length < 2) return;
        let heapSize = 0;

        //普通建堆
        // for(let i = 0;i<arr.length;i++){ //O(N)
        //     this.heapInsert(arr,i,comparator); //O(logN)
        //     heapSize++;
        // }

        for (let i = arr.length - 1; i >= 0; i--) { //稍微更快的建堆方法
            this.heapify(arr, i, arr.length, comparator);
            heapSize++;
        }

        this.swap(arr, 0, --heapSize)
        //排序
        while (heapSize > 0) { //O(N)
            this.heapify(arr, 0, heapSize, comparator); //O(logN)
            this.swap(arr, 0, --heapSize);
        }
    }

拓展:已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过K,并且K相对于数组来说比较小,请针对这个数据进行排序。

heapSortInK(arr: number[], k: number, comparator: Function) {
        //首先定义一个heapSize为k的堆,将数组中的前k个数加进去
        let heap = [];

        let index = 0
        for (let i = 0; i < k; i++) {
            heap[i] = arr[i];
            this.heapInsert(heap, i, comparator);
        }

        //然后将后续的数加入到堆中,然后弹出堆顶
        for (let i = k; i < arr.length; i++) {
            heap[i] = arr[i];
            arr[index++] = heap[0];
            this.swap(heap, i, 0);
            this.heapify(heap, 0, k, comparator);
        }

        for (let i = k - 1; i >= 0; i--) {
            arr[index++] = heap[0];
            this.swap(heap, i, 0)
            this.heapify(heap, 0, i, comparator)
        }
    }

基于比较的排序算法的 归纳总结

稳定性 是指相同数据之间在排序后是否能维持原来的先后,
冒泡排序和插入排序中相等不互换,因此稳定
归并排序先录入左边的数字也是稳定的(求小和算法中的归并排序不稳定,因为必须先录入右边的数字)
快速排序与选择一样会交换数字的位置,因此不稳定
堆排序 父子间的比较会互换位置,因此不稳定

时间复杂度空间复杂度稳定性
选择排序O(N^2)O(1)×
冒泡排序O(N^2)O(1)
插入排序O(N^2)O(1)
归并排序O(N*logN)O(N)
快速排序O(N*logN)O(logN)×
堆排序O(N*logN)O(1)×
  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值