归并排序

归并排序

  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   老菜农  阅读(56)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律

导航

统计信息

点击右上角即可分享
微信分享提示