归并排序

归并排序

  1. 整体上是递归,左边排好序+右边排好序+merge让整体有序

  2. 让其整体有序的过程里用来排外序方法

  3. 利用master公式来求解时间复杂度

  4. 当然可以用非递归实现

例:无序数组arr[L.....R]排序

master:T(N) = 2*T(N/2)+O(N),为什么是N呢,因为这个数组总长度是N,在merge方法中,排序实际上是左边走了一半N,右边走了一半N,加起来常数操作就是N

时间复杂度:O(Nlog(2,N)) 额外空间复杂度:O(N):由于每次开空间都是开了一个长度N的数组,用完就释放了

public static void process(int[] arr, int L, int R){
    if(L==R){
        return ;//如果这个数字只有1位直接返回就行
    }
    int mid = L+((R-L)>>1);//取中间下标
    process(arr, L, mid);//排序从小到大
    process(arr, mid+1, R);//排序从小到大
    merge(arr,L,mid,R);//归并方法
}
public static void merge(int[] arr, int L, int M, int R){
    int[] temp = new int[R-L+1];//临时数组,长度为l到r的元素个数,用于存储归并的答案
    int i = 0;//temp开始的下标
    int p1 = L;//第一个指针从数组最左端开始的下标
    int p2 = M+1;//第二个指针从中间的数后一个开始
    while(p1 <= M && p2 <= R){//如果指针1和指针2都没有越界
        //如果从Mid中间数分开数组,左边的第i个数比中间右边的第i个数大,那么temp的第i个数就是左边的第i个数,然后让左边的下标+1,反之就是右边,然后让右边的下标+1
        temp[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
    }
    //上述循环完后,p1如果没有越界,那么就是p2越界了,把剩下的左边数字全部加入tempt数组中,这个循环和下一个循环只会发生一个
    while(p1 <= M){
        temp[i++] = arr[p1++]
    }
    //上述大循环完后,p2数字没有越界,就是p1越界,把右边剩下的数字全部加入temp
    while(p2 <= R){
        temp[i++] = arr[p2++]
    }
    //上面两个循环的判断条件就是p1,p2两个指针一个越界一个没越界,他两个必然发生一个或者一个都不发生,所以不用管他俩是否都发生的情况,这么写就可以节省空间
    
    //将临时数组里面的内容拷贝回原数组
    for(int j = 0; j < tmp.length; j++){
        arr[L+j] = temp[j];
    }
}

例:小和问题

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和D

求[1, 3, 4, 2, 5]的小和 就是 1+1+3+1+1+3+4+2 = 16

也可以这么想,在第i个数右侧,有几个数比他大,那么这个数就加了几次

比如1的右侧有4个数比1大,就是4*1,3的右边有2个数比3大,就是2×3,4的右边有一个数比4大,就是1×4,5右边没数了

使用归并方法找小和

1、就上面的例子来说,首先我们找到中点,把这个数组分成[1,3,4] [2, 5]

2、[1,3,4]继续分,分成[1,3] [4],再分[1] [3] 我们先看1和3,1比右侧的3小,那么就小数加上一个1,右指针移动看1和4,1比4小,小数再加上一个1,左指针移动看3和4,3比4小,小数加一个3,指针越界,然后这左边就结束了

3、看右侧[2,5],继续分[2],[5],看2 5 ,2比5小,小数加一个2,指针越界,右边结束

4、我们继续看左边整体和右边整体,左边指针在最左侧1上,右边指针在2上,1小于2,加一个1,右边指针右移,1小于5,加一个1,左边指针右移,3大于2,右边指针右移,3小于5,加一个3,左边指针右移4小于5,加一个4,然后两边指针越界,结束。

5、最终得到小数位16

public static int process(int[] arr, int l, int r){
    if(l == r){
        return 0;
    }
    int mid = l + ((r -1) >> 1);
    return process(arr, l, mid)//左侧排序求小和的数量
        +  process(arr, mid + 1, r)//右侧排序求小和的数量
        +  merge(arr, l, mid, r);//左右排序号,然后合并求小和的数量
}
public static int merge(int[] arr, int L, int m, int r){
    int[] temp = new int[r-L+1];
    int i = 0;
    int p1 = L;
    int p2 = m + 1;
    int res = 0;
    //左组和右组都没有越界的情况下
    while(p1 <= m && p2 <= r){
        //求小和的目的
        //左组的数比右组的数组小,则小和=右组的长度-右组的指针+1乘左组的数本身,也就是排序完右组比左组那个数大的数的个数*左组的数本身。
        //如果左组没有比右组的小,增加的就是0
        res += arr[p1] < arr[p2] ? (r - p2 +1) * arr[p1] : 0;
        //如果左组的数比右组的大,那么拷贝左组,右组大拷贝右组,到新的数组中
        temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
    }
    while(p1 <= m){
        //如果左没越界,但右组越界了,就让左侧的数组全部拷贝到临时数组中。这里是不产生小和的
        temp[i++] = arr[p1++];
    }
    while(p2 <= r){
         //如果右没越界,但左组越界了,就让右侧的数组全部拷贝到临时数组中,不产生小和
        temp[i++] = arr[p2++];
    }
    //将临时数组中所有数,全都放到原数组中,达成排序的目的
    for(int j = 0; j < temp.length; i++){
        arr[L + j] = temp[i];
    }
    //返回小和
    return res;
}

例:逆序对问题

在一个数组中,左边的数比右边的大,则这两个数构成一个逆序对,请打印所有的逆序对

public static String process(int[] arr,int L,int R){
    if(L==R){
        return;
    }
    int mid = L+((R-L)>>1);
    return process(arr, L, M) +
           process(arr, M+1, R)+
           merge(arr, L, mid, R);
}
​
pubilc static String process(int[] arr, int L, int mid, int R){
    int i = 0;
    int p1 = 0;
    int p2 = 0;
    int temp = new int[R-L+1];
    String str = "";
    while(p1 <= mid && p2 <= R){
        str += arr[p1] < arr[p2] ? arr[p1]+""+arr[p2] : null;
        temp[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
    }
    while(p1 <= mid){
        temp[i++] = arr[p1++];
    }
    while(p2 <= R){
        temp[i++] = arr[p2++];
    }
    return str;
}

148. 排序链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode sortList(ListNode head) {
        //递归停止条件
        if(head == null || head.next == null){
            return head;
        }
        ListNode mid = getMidNode(head);
        ListNode newHead = mid.next;
        mid.next = null;//制空mid后面的,让原链表断成两个
        ListNode L = sortList(head);
        ListNode R = sortList(newHead);
        return merge(L, R);
    }
    //取得中点
    public ListNode getMidNode(ListNode head){
        if(head.next == null || head.next.next == null){
            return head;
        }
        ListNode fast = head.next.next;
        ListNode slow = head;
        while(fast.next != null && fast.next.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
    //归并两个链表
    public ListNode merge(ListNode L, ListNode R){
        ListNode dummyHead = new ListNode(-1);
        ListNode cur = dummyHead;
        while(L != null && R != null){
            if(L.val < R.val){
                cur.next = L;
                L = L.next;
            }else{
                cur.next = R;
                R = R.next;
            }
            cur = cur.next;
        }
        //如果节点数不是偶数,最后肯定会多一个出来,谁不是空就把谁放当前链表最后一个
        cur.next = L != null ? L : R;
        return dummyHead.next;
​
    }
    
}

posted on 2022-08-18 22:38  老菜农  阅读(43)  评论(0编辑  收藏  举报

导航