排序算法详解(堆,归并,快速排序最简及理解写法)

十大排序算法和复杂度

常见排序的详解

只讲解真实场景中常用的, 简单的就不分析了大家稍微看一下就行

快速排序

快排的思想主要就是每次把一个位置放好后, 可以把数组分成两半, 递归处理子问题即可

空间复杂度OlogN 分析: 每次都分成两半处理子问题, 可以把数组看成树的节点, 则变成整个递归相当于一棵二叉树树高, 即OlogN

时间复杂度优化后基本稳定在ONlogN了, 这个分析过程不要求掌握比较复杂, 不优化的最差ON2 

    //调用 QuickSort(nums, 0, nums.length - 1)
    
    void QuickSort(int[] nums, int l, int r){
        if(l >= r) return ;
        //idx即放好了这个位置, 下面递归
        int idx = partition(nums, l, r);
        QuickSort(nums, l, idx - 1);
        QuickSort(nums, idx + 1, r);
    }
    //每次把一个数放到他正确的位置
    int partition(int[] nums, int l, int r){
        int midIdx = (l + r) >> 1; //这边主要就是优化一下, 否则每次选左边的话在有序数组中会On2复杂度
        swap(nums, l, midIdx);  //然后把他放到左边
        int pivot = nums[l];
        int i = l + 1, j = r;
        while(true){
            //小于主元的放在左边
            while(i <= j && nums[i] < pivot) i ++;
            //大于主元的放在右边
            while(i <= j && nums[j] > pivot) j --;
            if(i >= j) break;
            swap(nums, i, j);
            i ++; j --;
        }
        //这边是j的原因是因为在上面的过程中, i最后一定停在>=pivot位置, 我们是不能换>pivot的否则错误
        swap(nums, l, j);
        return j;
    }
    void swap(int[] nums, int a, int b){
        int t = nums[a];
        nums[a] = nums[b];
        nums[b] = t;
    }

快排还有另外一种用途就是快速选择

 对于本题可以利用partition来达到ON的一个复杂度, 常规做法为堆排序ONlogK这边就不给出堆排序的代码了

复杂度分析:partition函数是一个N的复杂度, 我们这边是优化过的所以是每次partition后把数组拆成两半,后面就是N/2的复杂度, 加起来就是N+N/2+N/4+N/8+...+1这个公式计算完是ON的复杂度

    public int findKthLargest(int[] nums, int k) {
        //我们可以基于partition每次都可以让一个数字去到排序后的位置, 利用这种思想来
        //进行快速选择, 也就是用快速选择找到第k大的数, 他partition后的位置应该在len - k
        int len = nums.length;
        int target = len - k;
        int l = 0, r = len - 1;
        while(true){
            //把一个位置的元素归位
            int partition = partition(nums, l, r);
            //正好是归到了target位置则他就是第k大
            if(partition == target) return nums[partition];
            //大于这个位置代表我们要找的数字在左边
            else if(partition > target) r = partition - 1;
            //反之右边
            else l = partition + 1;
        }
    }
    public int partition(int[] nums, int l, int r){
        int midIdx = (l + r) >> 1;
        swap(nums, midIdx, l);
        int pivot = nums[l];
        int i = l + 1, j = r;
        while(true){
            while(i <= j && nums[i] < pivot) i ++;
            while(i <= j && nums[j] > pivot) j --;
            if(i >= j) break;
            swap(nums, i, j);
            i ++; j --;
        }
        swap(nums, l, j);
        return j;
    }
    public void swap(int[] nums, int a, int b){
        int t = nums[a];
        nums[a] = nums[b];
        nums[b] = t;
    }

堆排序

堆排序则基于树的思想, 建堆的时间复杂度为ON这个必须注意, 大家很多人都会以为跟log有关系其实是无关的, 想象成一颗巨大的树就可以分析出来了

空间复杂度O1, 因为这个是原地操作的算法

时间复杂度ONlogN, 堆排序的自调节复杂度为OlogN

   void HeapSort(int[] nums){
        int size = nums.length;
        //从最后一个非叶子节点调节
        for(int i = size / 2 - 1; i >= 0; i --){
            adjust(nums, i, size);
        }
        for(int i = size - 1; i >= 1; i --){
            //每次把一个最大的放在最后面, 调节根节点调节完就排序完了
            swap(nums, i, 0);
            //这边可以发现我们每次size都是比前面一次小了1哦
            adjust(nums, 0, i);
        }
    }
​
    void adjust(int[] nums, int root, int size){
        int child = 2 * root + 1;
        while(true){
            //越界跳出
            if(child >= size) break;
            //选大儿子
            if(child + 1 < size && nums[child + 1] > nums[child]) child ++;
            //儿子大就交换
            if(nums[root] <= nums[child]){
                swap(nums, root, child);
                root = child;
                child = 2 * root + 1;
            }
            else break;
        }
    }
​
    void swap(int[] nums, int a, int b){
        int t = nums[a];
        nums[a] = nums[b];
        nums[b] = t;
    }

归并排序

归并排序的思想则为处理递归到最小的粒度数组处理子问题, 子问题合并后想上归合起来继续处理即可

空间复杂度ON, 这边递归栈的高度同快速排序是OlogN, 但是考虑到归并过程的额外数组, 故为On

时间复杂度为ONlogN

    //调用MergeSort(nums, 0, nums.length - 1)
    void MergeSort(int[] nums, int l, int r){
        if(l >= r) return ;
        //对半拆分
        int mid = l + r >> 1;
        MergeSort(nums, l, mid);  //注意这里一定必须右边是mid 否则会出错
        MergeSort(nums, mid + 1, r);
        Merge(nums, l, mid, r);
    }
    void Merge(int[] nums, int l, int mid, int r){
        int i = l,  j = mid + 1;
        int[] temp = new int[r - l + 1];
        int cnt = 0;
        //这里是归并的过程, 就是对比左右两个子数组排序一下
        while(i <= mid && j <= r){
            if(nums[i] < nums[j]) temp[cnt ++] = nums[i ++];
            else temp[cnt ++] = nums[j ++];
        }
        while(i <= mid) temp[cnt ++] = nums[i ++];
        while(j <= r) temp[cnt ++] = nums[j ++];
        for(i = 0; i < temp.length; i ++) nums[l ++] = temp[i];
    }
    void swap(int[] nums, int a, int b){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }

冒泡排序

public void bubbleSort(int[] nums){
        int n = nums.length;
        for(int i = 0; i < n; i++){
            boolean flag = false;
            for(int j = n - 1; j > i;j--){
                if(nums[j - 1] > nums[j]){
                    int temp = nums[j - 1];
                    nums[j - 1] = nums[j];
                    nums[j] = temp;
                    flag = true;
                }
            }
             if(flag == false) return ;
        }
    }

插入排序

    public void insertSort(int[] nums){
        int n = nums.length;
        for(int i = 0; i < n; i++){
            int temp = nums[i];
            int j = i - 1;
            while(j >= 0 && nums[j] > temp){
                nums[j + 1] = nums[j];
                j--;
            }
            nums[j + 1] = temp;
        }
    }

选择排序

    public void selectSort(int[] arr){
        int min;
        for(int i = 0;i<arr.length;i++){
            min = i;
            for(int j = i;j<arr.length;j++){
                if(arr[j] < arr[min]){
                    min = j;
                }
            }
            if(min != i) {
                int temp = arr[i];
                arr[i] = arr[min];
                arr[min] = temp;
            }
        }
    }

桶排序

思想:nums[i] 放到桶里面,对于范围小的好用, 即创建一个int[范围], 然后根据范围取一下就排序完了

总结

主要掌握快速排序, 归并排序, 堆排序就可以, 复杂度必须要会分析!

  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值