归并排序
归并排序
-
整体上是递归,左边排好序+右边排好序+merge让整体有序
-
让其整体有序的过程里用来排外序方法
-
利用master公式来求解时间复杂度
-
当然可以用非递归实现
例:无序数组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;
}
/**
* 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;
}
}