归并排序
归并排序
-
整体上是递归,左边排好序+右边排好序+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; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律