归并排序
归并排序基础
归并排序原理
运用递归思想,将需要排序的数组一分为二,把分好的两部分进行排序、合并
排序过程的伪代码:
- 整个归并排序算法可以看成两部分:将数组递归分解,将两部分数组进行合并
- 其中,使用MergeSort函数进行排序,传入参数为:arr(待排序数组),l(待排序数组左侧),r(待排序数组右侧)
- MergeSort函数:
- 找出需要排序数组的中间部分,将数组一分为二
- 运用MergeSort函数对左右两边的数组分别进行排序(递归过程)
- 最后用merge函数合并已排序好的两数组
- 关于递归:归并排序中,递归的主要作用就是将数组进行分解,简化。MergeSort函数整体就是实现归并排序的过程,使用函数时应该把MergeSort函数看成是将arr数组中的l到r进行归并排序,作用是排序数组。要将重点放在函数的作用,不要过多关注函数内部的实现,以免发生混淆。
归并过程
- merge函数的实现过程:
- 复制传入数组
- 设置两个指针,i、j
- 遍历数组
- 比较arr[i]和arr[j]的大小,决定它们摆放的先后位置,再进行i++/j++
归并实现
package MergeSort;
import java.util.Arrays;
public class MergeSort2 {
//设置一个空的构造函数
private MergeSort2() {
}
//设置两个sort函数,public供外界访问,private用来真正实现sort功能
public static <E extends Comparable<E>> void sort(E[] arr) {
sort(arr,0,arr.length-1);
}
private static <E extends Comparable<E>> void sort(E[] arr,int l,int r) {
if(l>=r)//递归终止条件
return;
int mid=(r+l)/2;
sort(arr,l,mid);
sort(arr,mid+1,r);
merge(arr,l,mid,r);
}
public static<E extends Comparable<E>> void merge(E[] arr,int l,int mid,int r) {
E[] tmp=Arrays.copyOfRange(arr, l, r+1);
int i=l,j=mid+1;
for(int k=l;k<=r;k++) {
if(j>r) {
arr[k]=tmp[i-l];
i++;
}
else if(i>mid) {
arr[k]=tmp[j-l];
j++;
}
//tmp[i-l]中,i的初始值为l,比较是需先减去偏移量l
else if(tmp[i-l].compareTo(tmp[j-l])<=0) {
arr[k]=tmp[i-l];
i++;
}
else {
arr[k]=tmp[j-l];
j++;
}
}
}
public static void main(String[] args) {
int n=100000;
Integer[] arr=ArrayGenerator.generateRandomArray(n, n);
SortingHelper.sortTest("MergeSort", arr);
}
}
结果
归并排序复杂度分析
假定要对数组容量为8的数组进行排序,此图为整个递归的过程
T为运算次数,由此得出整个复杂度为O(nlogn)级别
归并排序算法的优化
在有序数组中优化为O(n)级别
只需比较arr[mid]和arr[mid+1]的大小,决定是否进行merger操作,避免进行时间上的浪费
private static <E extends Comparable<E>> void sort(E[] arr,int l,int r) {
if(l>=r)//递归终止条件
return;
int mid=(r+l)/2;
sort(arr,l,mid);
sort(arr,mid+1,r);
if(arr[mid].compareTo(arr[mid+1])>0)//新增的判断语句
merge(arr,l,mid,r);
}
若需要排序的数组为有序数组,会直接变成O(n)级别的复杂度
使用插入排序法优化
由于插入排序法是O(n^2)级别的算法,在一定范围内,使用插入排序法会更快,由此可用来优化归并
修改部分代码
private static <E extends Comparable<E>> void sort(E[] arr,int l,int r) {
// if(l>=r)//递归终止条件
// return;
if(r-l<=15) {//规模小于15时,使用插入排序法
InsertionSort.sort(arr,l,r);
return ;
}
int mid=(r+l)/2;
sort(arr,l,mid);
sort(arr,mid+1,r);
if(arr[mid].compareTo(arr[mid+1])>0)
merge(arr,l,mid,r);
}
对归并排序的内存进行优化
之前的代码中,merge函数进行归并时会重复开辟tmp空间,造成空间浪费,在使用sort进行排序之前,提前开辟好一个空间可避免空间的浪费
public static <E extends Comparable<E>> void sort2(E[] arr) {
E[] tmp=Arrays.copyOfRange(arr, 0, arr.length);//提前开辟好空间
sort2(arr,0,arr.length-1,tmp);
}
private static <E extends Comparable<E>> void sort2(E[] arr,int l,int r,E[] tmp) {
// if(l>=r)//递归终止条件
// return;
if(r-l<=15) {
InsertionSort.sort(arr,l,r);
return ;
}
int mid=(r+l)/2;
sort2(arr,l,mid,tmp);
sort2(arr,mid+1,r,tmp);
if(arr[mid].compareTo(arr[mid+1])>0)
merge2(arr,l,mid,r,tmp);
}
public static<E extends Comparable<E>> void merge2(E[] arr,int l,int mid,int r,E[] tmp) {
System.arraycopy(arr, l, tmp, l, r-l+1);//保持arr和tmp数组中,l到r位置的数据一致
int i=l,j=mid+1;
for(int k=l;k<=r;k++) {
if(j>r) {
arr[k]=tmp[i];
i++;
}
else if(i>mid) {
arr[k]=tmp[j];
j++;
}
else if(tmp[i].compareTo(tmp[j])<=0) {
arr[k]=tmp[i];
i++;
}
else {
arr[k]=tmp[j];
j++;
}
}
}
结果
自低而上的归并排序
自底向上的归并排序
实质就是从两两开始归并,到四个四个,六个六个…以此类推,相当于自顶向下归并的应该逆过程
代码
//自底向上归并排序
public static <E extends Comparable<E>> void sortBU(E[] arr){
E[] tmp=Arrays.copyOf(arr, arr.length);
int n=arr.length;
for(int sz=1;sz<n;sz+=sz) {
//遍历合并区间的起始位置
//合并[i,i+sz-1]和[i+sz,Math.min(i+sz+sz-1, n-1)]中的元素
for(int i=0;i+sz<n;i+=sz+sz) {
merge2(arr,i,i+sz-1,Math.min(i+sz+sz-1, n-1),tmp);
}
}
}
注意每次归并开始和结束区间的位置
结果:
解决数组中的逆序对数量
在数组的merge操作中,如arr[j]<arr[i]则要进行一次swap,此时,arr[j]便于arr[i,mid]处的数都形成了逆序对,按此加和其中的数字个数,并返回即可
代码
public class Solution {
private int rs=0;
public int reversePairs(int[] nums) {
int[] tmp=Arrays.copyOf(nums, nums.length);
rs=0;
sort(nums,0,nums.length-1,tmp);
return rs;
}
private void sort(int[] arr,int l,int r,int[] tmp) {
if(l>=r)//递归终止条件
return;
int mid=(r+l)/2;
sort(arr,l,mid,tmp);
sort(arr,mid+1,r,tmp);
if(arr[mid]>arr[mid+1])
merge(arr,l,mid,r,tmp);
}
public void merge(int[] arr,int l,int mid,int r,int[] tmp) {
System.arraycopy(arr, l, tmp, l, r-l+1);
int i=l,j=mid+1;
for(int k=l;k<=r;k++) {
if(j>r) {
arr[k]=tmp[i];
i++;
}
else if(i>mid) {
arr[k]=tmp[j];
j++;
}
else if(tmp[i]<=tmp[j]) {
arr[k]=tmp[i];
i++;
}
else {
rs+=mid-i+1;
arr[k]=tmp[j];
j++;
}
}
}
}