排序算法(六):归并排序算法及其优化分析

8 篇文章 0 订阅

1.归并排序算法

将两个有序数列合并成一个有序数列称为归并......

归并排序算法:将无序数组不断的等分成两份,直到分割成单位元素之后,再利用归并的思想,从下往上不断合并成有序数组的过程。主要分为两种:自底向上的归并排序和自上向下的归并排序。

注意:归并排序过程中,需要开辟一个相同大小的临时空间来辅助归并排序的进行。对于两个有序数组是怎么合并到一起的呢?我们来看下面的过程:

原始数组:    2   3   6   8   1   4   5   7         -- 使用索引 i 表示排好序后插入的元素位置的后一位,每次插入都需要维护

临时空间:    2   3   6   8   1   4   5   7         -- 使用索引 m n 分别表示子数组最左侧的待比较元素,每次比较都需要维护其中一个

第一轮:两个有序数组的第一个元素进行比较,较小的元素放到原始数组的第一位;

原始数组:    1   3   6   8   1   4   5   7         -- 索引 i 加1

临时空间:    2   3   6   8     4   5   7         -- 后一个子数组索引值+1

第二轮:继续比较剩余元素的最左侧元素

原始数组:    1   2   6   8   1   4   5   7         -- 索引 i 加1

临时空间:     3   6   8     4   5   7         -- 前一个子数组索引值+1

依次类推,直到归并排序完成。

1)自上向下的归并过程如下:递归 + 合并

public class MergeSort02 {
	public static void main(String[] args) {
		int[] arr = {2,5,6,3,8,4,9,1};
		mergeSortUp2Down(arr,0,arr.length-1);
		for (int i : arr) {
			System.out.print(i + ",");
		}
	}
	
	/**
	 * 编写有序数组的归并逻辑
	 * @param arr 包含两个有序区间的数组
	 * @param start 第1个有序区间的起始地址
	 * @param mid 第1个有序区间的结束地址。也是第2个有序区间的起始地址
	 * @param end 第2个有序区间的结束地址。
	 */
	public static void merge(int[] arr,int start,int mid,int end) {
		//开辟临时区间,用作汇总两个有序区间
		int[] temp = new int[end-start+1];
		//合并两个有序区间需要维护三个变量
		int i = start;
		int j = mid + 1;
		int k = 0;           //临时区域的索引
		
		//自定义两个有序数组的合并逻辑 + 排序逻辑
		while(i<=mid && j<=end) {
			if(arr[i] <= arr[j])
				temp[k++] = arr[i++];    //将较小的元素插入到临时空间中,同时维护变量
			else
				temp[k++] = arr[j++];
		}
		
		//当一部分有序数组提前结束的话,则将剩下的值一次赋给temp[]
		while(i <= mid)
	        temp[k++] = arr[i++];

	    while(j <= end)
	        temp[k++] = arr[j++];
		
		//将排序好的数组,全部整合到数组a中
		for(i=0 ; i<k ; i++) {
			arr[start + i] = temp[i];
		}
		
		//清空临时空间
		temp = null;
	}
	
	/**
	 * 自上向下的归并排序
	 * @param arr 待排序的数组
	 * @param start 数组的起始索引
	 * @param end 数组的结束索引
	 */
	public static void mergeSortUp2Down(int[] arr,int start,int end) {
		//定义递归的终止条件
		if(arr==null || start>=end) {
			return;
		}
		
		int mid = (start+end)/2;
		//此时r=mid,数组开始二分至最小单位。只有递归到底时,才会继续往下执行
		mergeSortUp2Down(arr, start, mid);
		mergeSortUp2Down(arr, mid+1, end);
		//从单个元素开始,不断向上将两个有序数组合并成一个有序空间 
		merge(arr,start,mid,end);
	}
}

2)自底向上的归并过程如下:迭代 + 合并

public class MergerSort01 {
	public static void main(String[] args) {
		int[] arr = {2,5,6,3,8,4,9,1,10};
		mergeSortDown2Up(arr,arr.length);
		for (int i : arr) {
			System.out.print(i + ",");
		}
	}
	/**
	 * 具体的合并逻辑
	 * @param arr 包含两个有序区间的数组
	 * @param start 第1个有序区间的起始地址
	 * @param mid 第1个有序区间的结束地址。也是第2个有序区间的起始地址
	 * @param end 第2个有序区间的结束地址。
	 */
	public static void merge(int[] arr,int start,int mid,int end) {
		//开辟临时区间,用作汇总两个有序区间
		int[] temp = new int[end-start+1];
		//合并两个有序区间需要维护三个变量
		int i = start;
		int j = mid + 1;
		int k = 0;           //临时区域的索引
		
		//自定义两个有序数组的合并逻辑 + 排序逻辑
		while(i<=mid && j<=end) {
			if(arr[i] <= arr[j])
				temp[k++] = arr[i++];    //将较小的元素插入到临时空间中,同时维护变量
			else
				temp[k++] = arr[j++];
		}
		
		//当一部分有序数组提前结束的话,则将剩下的值一次赋给temp[]
		while(i <= mid)
	        temp[k++] = arr[i++];

	    while(j <= end)
	        temp[k++] = arr[j++];
		
		//将排序好的数组,全部整合到数组a中
		for(i=0 ; i<k ; i++) {
			arr[start + i] = temp[i];
		}
		
		//清空临时空间
		temp = null;
	}
	
	/**
	 * 对长度为 n 的数组进行合并操作
	 * @param arr 待排序数组
	 * @param len 数组的长度
	 * @param n 子数组的长度
	 */
	public static void merge_Group(int[] arr,int len,int n) {
		int i;               //定义全局变量,记录排序数组的索引位置
		int len2 = 2*n;      //两个相邻子数组的长度
		
		//将两个相邻的子数组合并到一起
		for(i=0 ; i+len2-1<len ; i+=len2) {
			merge(arr, i, i+n-1, i+len2-1);
		}
		
		//若 i+gap-1 < len-1,则剩余一个子数组没有配对。则将该数组合并到已排序的数组中
		if(i+n-1 < len-1) {
			 merge(arr, i, i+n-1, len-1);
		}
	}
	
	/**
	 * 归并排序:自底向上
	 * @param arr 待排序的数组
	 * @param len 数组的长度
	 */
	public static void mergeSortDown2Up(int[] arr,int len) {
		int n = 0;
		//判断数据是否合法
		if(arr==null || len<=0) {
			return;
		}
		//循环迭代,进行递归排序。初始数组长度为1
		for(n=1 ; n<len ; n*=2) {
			merge_Group(arr,len,n);
		}
	}
}

注:自底向上的归并排序由于没有使用数组索引的特性,因此更加适合于链表的归并排序。

2.算法优化分析

1)对于近乎有序的数组,arr[mid] <= arr[mid+1] 的情况更容易出现,在进行 if 判断后,可以省去 merge 操作。同时 if 判断本身也会损耗时间,但对于这种情况利大于弊;

if(arr[mid] > arr[mid+1]) {
	merge(arr,start,mid,end);
}

2)几乎对于所有的高级排序算法,都存在一种优化。即在递归将要结束时,如果递归的范围较小,则可以使用插入排序的方式代替归并排序。

插入排序        O(N^2)          a*N^2+C

归并排序        O(NlogN)      b*NlogN+C

已知 a<b,当 N 小到一定范围时,会出现 a*N^2 < b*NlogN 的情况

3.时间复杂度分析

归并排序每次递归都将数组均匀等分,由 2^m=N 可知,归并排序的深度为 m=lgN。由于每层都需要 O(N) 的时间复杂度。因此归并排序的时间复杂度为 O(NlgN) 级别的。

4.空间复杂度分析

自上向下的归并排序需要存储排序数组的临时空间,因此需要O(N)的空间复杂度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值