手撕 快速排序 / 归并排序

导读:本篇文章讲解 手撕 快速排序 / 归并排序,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

链接
题目:
在这里插入图片描述

1. 快速排序

基本思想:
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序的详细原理

1.1 时间复杂度:

平均时间复杂度是 O(n logn)

最好情况:每一次切分选择的基准数字刚好将当前序列等分成两部分;如果把数组的切分看做是一个树,那么下图就是它的最优情况的图示,共切分了O(logn)次,所以,最优情况下快速排序的时间复杂度为O(n logn);
在这里插入图片描述

最坏情况: 每一次切分选择的基准数字是当前序列中最大数或者最小数,这使得每次切分都只比上次少一个元素,那么总共就得切分n次,所以,最坏情况下,快速排序的时间复杂度为O(n^2);

注意:

  1. 先走指针,再走左指针 !最后返回的partition也是右指针;
  2. sort 是递归分界;partition是返回新的基准值,递归拆分时不算上partiton分界值!
    sort(nums,lo,partition-1);
    sort(nums,partition+1,hi);
    而归并排序中:
    sort(nums,lo,mid); // 是mid而不是mid-1
    sort(nums,mid+1,hi);
class Solution {
    public int[] sortArray(int[] nums) {
        int lo=0;
        int hi=nums.length-1;
        sort(nums,lo,hi);
        return nums;
    }
	// 以分界值为界分割数组
    void sort(int[] nums,int lo,int hi){
        if(lo>=hi){ 
            return;
        }
        //前序遍历 得到分界值索引
        int partition=partition(nums,lo,hi);
        sort(nums,lo,partition-1); // 递归
        sort(nums,partition+1,hi);
    }

	// 返回新的分界值、同时排序
	// 指针从两边往中间走!
    int partition(int[] nums,int lo,int hi){
        // 分界值
        int key=nums[lo];
        //指针
        int left=lo; // 第一个是分界值要跳过
        int right=hi+1;
        while(true){
	        // 先走右 !直到小于key则等待交换
            while(nums[--right]>key){ 
                if(left>=right){
                    break; // 越界跳出while
                }
            }
            // 再走左 ! 直到大于Key则等待交换
            while(nums[++left]<key){ // 再走左 !
                if(left>=right){
                    break; // 越界跳出while
                }
            }
            // 指针重合则跳出while(true)
            if(left>=right){ 
                break;  
            }
            swap(nums,left,right); // ※
        }
        // 跳出while(true),首元素和指针重合元素(分界节点)
        swap(nums,lo,right);
        return right; // 返回必须右边
    }

    void swap(int[] nums,int i,int j){
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
}

2. 归并排序:

基本思想:
先拆分数组为两个子序列,然后将已有序的子序列合并,得到完全有序的序列;
即先使每个子序列有序,再使子序列段间有序;

注意: lo、mid、hi 都是索引 !在 merge中判断时有等号 !!!

2.1 时间复杂度:

每次固定以mid值来切分,所以切分的复杂度是O(logn),所以总共是O(n logn)
注意辅助数组的索引从 lo 开始,而不是从0 开始!

class Solution {
    // 辅助数组
    int[] s;
    public int[] sortArray(int[] nums) {
        s=new int[nums.length];
        int lo=0;
        int hi=nums.length-1;
        sort(nums,lo,hi);
        return nums;
    }

    void sort(int[] nums,int lo,int hi){
        // base case
        if(lo>=hi){
            return;
        }
        int mid=lo+(hi-lo)/2;
        sort(nums,lo,mid); // mid而不是mid-1
        sort(nums,mid+1,hi);
        //后序遍历
        merge(nums,lo,mid,hi);
    }
    //合并两个数组
    void merge(int[] nums,int lo,int mid,int hi){
        // 三个指针
        int i=lo;
        int p1=lo;
        int p2=mid+1;
        while( p1<=mid && p2<=hi ){
            if(nums[p1]<=nums[p2]){
                s[i++]=nums[p1++];
            }else{
                s[i++]=nums[p2++];
            }
        }
        // 有一边到头,到头的指针最后会++,超过范围
        while(p1<=mid){
            s[i++]=nums[p1++];
        }
        while(p2<=hi){
            s[i++]=nums[p2++];
        }

        //拷贝回原数组  注意j<=hi而不是< !
        for(int j=lo;j<=hi;j++){
            nums[j]=s[j];
        }
    }

}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/89236.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!