七大排序之归并排序


什么是归并排序?

归并排序的思想:将原数组不断拆分,一直拆到每个子数组只有一个元素时,第一阶段结束。然后开始“并”,将相邻两个数组合并为一个有序的数组,直到整个数组有序

归并排序时间复杂度:归并排序是一个稳定的 O(nlogn) 排序算法,此处的稳定指的是时间复杂度稳定且归并排序也是一个稳定性排序算法

时间复杂度稳定:归并排序拆分的过程类似于树的结构,递归的深度就是我们拆分数组所用的时间,就是树的深度,为O(logn)。而合并两个数组的过程 merge 操作就是一个数组的遍历过程,为O(n)。

归并排序图解:

在这里插入图片描述
合并过程图解:

归并

归并排序代码

代码如下:

public static void mergeSort(int[] arr){
        mergeSortInternal(arr,0,arr.length-1);
    }
    private static void mergeSortInternal(int[] arr, int l, int r) {
        if (l>=r){
            return;
        }
        int mid=l+(r-l)/2;
        //将原数组拆分成左右两个小区间
        mergeSortInternal(arr,l,mid);
        mergeSortInternal(arr,mid+1,r);
        merge(arr,l,r,mid);
    }
    private static void merge(int[] arr, int l, int r, int mid) {
        //1.定义一个临时数组,将arr里的元素拷贝进去
        int[] aux=new int[r-l+1];
        for (int i = 0; i < aux.length; i++) {
            aux[i]=arr[i+l];
        }
        int i=l;  //i为左侧小数组的起始位置
        int j=mid+1; //j为右侧小数组的起始位置
        for (int k=l;k<=r;k++){ //k遍历的是arr数组
            if (i>mid){  //左侧小数组已经处理完了,直接将右侧剩下的元素拷贝过去
                arr[k]=aux[j-l];
                j++;
            }else if(j>r){ //右侧小数组已经处理完了,直接将左侧剩下的元素拷贝过去
                arr[k]=aux[i-l];
                i++;
            }else if(aux[i-l]<=aux[j-l]){//当左侧区间的元素大于右侧区间的元素时
                arr[k]=aux[i-l];
                i++;
            }else{
                arr[k]=aux[j-l];
                j++;
            }
        }
    }

归并排序的优化:

  • 当左右两个子区间走完子函数后,如果此时 arr[mid] < arr[mid+1],说明 arr[mid] 已经时左区间的最大值了,arr[mid+1] 已经是右区间的最小值了,整个区间已经有序,不需要再执行 merge 操作了!!!
  • 在小区间上,我们可以直接使用插入排序来优化,可以减少递归的次数!!!

优化后的代码如下:

public static void mergeSort(int[] arr){
        mergeSortInternal(arr,0,arr.length-1);
    }
    private static void mergeSortInternal(int[] arr, int l, int r) {
        //优化1:小区间上使用插入排序
        if (r-l<=15){
            insertionSort(arr,l,r);
            return;
        }
        int mid=l+(r-l)/2;
        mergeSortInternal(arr,l,mid);
        mergeSortInternal(arr,mid+1,r);
        //优化2:只有两个区间还存在元素先后顺序不同时才合并
        if (arr[mid]>arr[mid+1]) {
            merge(arr, l, r, mid);
        }
    }
    private static void merge(int[] arr, int l, int r, int mid) {
        int[] aux=new int[r-l+1];
        for (int i = 0; i < aux.length; i++) {
            aux[i]=arr[i+l];
        }
        int i=l;
        int j=mid+1;
        for (int k=l;k<=r;k++){
            if (i>mid){
                arr[k]=aux[j-l];
                j++;
            }else if (j>r){
                arr[k]=aux[i-l];
                i++;
            }else if (aux[i-l]<=aux[j-l]){
                arr[k]=aux[i-l];
                i++;
            }else{
                arr[k]=aux[j-l];
                j++;
            }
        }
    }
    public static void insertionSort(int[] arr,int l,int r){
        for (int i = l+1; i <=r ; i++) {
            for (int j = i; j >0 ; j--) {
                if (arr[j]>=arr[j-1]){
                    break;
                }else{
                    swap(arr,j,j-1);
                }
            }
        }
    }
    public static void swap(int[] arr,int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }

归并排序相关习题

148.排序链表

题目描述如下:

在这里插入图片描述
代码如下:

public ListNode sortList(ListNode head){
        if (head==null||head.next==null){
            return head;
        }
        ListNode mid=getMidNode(head);
        ListNode rightNode=mid.next;
        mid.next=null;
        //递归拆分链表
        ListNode l=sortList(head);
        ListNode r=sortList(rightNode);
        return merge(l,r);
    }
    //合并
    private ListNode merge(ListNode l, ListNode r) {
        ListNode dummyHead=new ListNode(0);
        ListNode cur=dummyHead;
        while (l!=null&&r!=null){
            if (l.val<r.val){
                cur.next=l;
                l=l.next;
                cur=cur.next;
            }else{
                cur.next=r;
                r=r.next;
                cur=cur.next;
            }
        }
        if (l==null){
            cur.next=r;
        }
        if (r==null){
            cur.next=l;
        }
        return dummyHead.next;
    }
    //获取链表中间节点(快慢指针)
    public ListNode getMidNode(ListNode head){
        ListNode fast=head.next.next;
        ListNode low=head;
        while (fast!=null&&fast.next!=null){
            fast=fast.next.next;
            low=low.next;
        }
        return low;
    }

剑指 Offer 51.数组中的逆序对

题目描述如下:

在这里插入图片描述
解题思路:

在这里插入图片描述
代码如下:

public int reversePairs(int[] nums) {
        return reversePairsHelper(nums,0,nums.length-1);
    }
    private int reversePairsHelper(int[] nums, int l, int r) {
        if (l>=r){
            return 0;
        }
        int mid=l+(r-l)/2;
        //1.先求出左区间的逆序对个数
        int left=reversePairsHelper(nums, l, mid);
        //2.求出右区间的逆序对个数
        int right=reversePairsHelper(nums, mid+1, r);
        if (nums[mid]>nums[mid+1]){
            //如果此时左右区间还存在逆序,合并
            return merge(nums,l,mid,r)+left+right;
        }
        //此时左右区间已经有序,逆序对个数就是左右区间逆序对个数之和
        return left+right;
    }
    //合并,并且返回合并过程中逆序对的个数
    private int merge(int[] nums, int l, int mid, int r) {
        //合并过程中产生的逆序对
        int count=0;
        int[] aux=new int[r-l+1];
        for (int i = 0; i < aux.length; i++) {
            aux[i]=nums[i+l];
        }
        int i=l;
        int j=mid+1;
        for (int k=l;k<=r;k++){
            if (i>mid){
                nums[k]=aux[j-l];
                j++;
            }else if (j>r){
                nums[k]=aux[i-l];
                i++;
            }else if (aux[i-l]<=aux[j-l]){
                nums[k]=aux[i-l];
                i++;
            }else{
                //左区间元素大于右区间元素,构成逆序对
                //记录逆序对的个数
                count+=mid-i+1;
                nums[k]=aux[j-l];
                j++;
            }
        }
        return count;
    }

总结

以上就是今天要讲的内容,本文介绍了七大排序中的归并排序,包括归并排序的思路、代码和相关习题,如果你觉得有收获的话,那就留下你的👍吧!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

怎样让大排不硬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值