归并排序算法

归并排序基础

归并排序原理

运用递归思想,将需要排序的数组一分为二,把分好的两部分进行排序、合并

在这里插入图片描述
排序过程的伪代码:

在这里插入图片描述

  • 整个归并排序算法可以看成两部分:将数组递归分解将两部分数组进行合并
  • 其中,使用MergeSort函数进行排序,传入参数为:arr(待排序数组),l(待排序数组左侧),r(待排序数组右侧)
  • MergeSort函数:
  1. 找出需要排序数组的中间部分,将数组一分为二
  2. 运用MergeSort函数对左右两边的数组分别进行排序(递归过程
  3. 最后用merge函数合并已排序好的两数组
  • 关于递归:归并排序中,递归的主要作用就是将数组进行分解,简化。MergeSort函数整体就是实现归并排序的过程,使用函数时应该把MergeSort函数看成是将arr数组中的l到r进行归并排序,作用是排序数组。要将重点放在函数的作用,不要过多关注函数内部的实现,以免发生混淆。

归并过程

  • merge函数的实现过程:
  1. 复制传入数组
  2. 设置两个指针,i、j
  3. 遍历数组
  4. 比较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++;
			}
				
			}
				
		}
	
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值