算法导论第二章之归并排序

本文探讨了归并排序的分治策略,包括递归实现的三个关键问题:终止条件、数组拆分和合并。还介绍了非递归版本,考虑结合多线程提升效率,并提出在小规模问题时切换到插入排序的优化方案,以适应不同情况。时间复杂度分析表明,归并排序的时间复杂度为O(nlog2 n),在已排序或接近排序的数组中,插入排序可能更优。
摘要由CSDN通过智能技术生成

归并算法即一种分治策略,将大问题划分为n个小问题,然后对n小问题求解组合成大问题的解。

在使用递归的分治排序中,涉及三个问题:一、小问题的不可划分的或者终止条件;二、数组的拆分;三、已排序的两个数组的合并。

以下代码是不使用递归的分治排序算法,考虑可以结合多线程,使速度更快。

package main;

import util.MRand;
import util.MTime;

/***
 * 归并排序
 * @author Administrator
 *
 */
public class MergeSort extends Thread {
	/**
	 * 归并排序进行升序排序,不使用哨兵
	 * @param a待排序数组
	 */
	private static void sortAsc(int[] a){
		if (a.length<=1){
			return ;
		}
		//拆分左右数组部分
		int leftLength = a.length/2;//左边部分的数组长度
		int rightLength = a.length-a.length/2;//右边部分的数组长度
		int L[] = new int[leftLength];
		int R[] = new int[rightLength];
		for(int i = 0;i<leftLength;i++){//赋值
			L[i]=a[i];
		}
		for(int i = 0;i<rightLength;i++){//赋值
			R[i] = a[leftLength+i];
		}
		sortAsc(L);
		sortAsc(R);
		int l = 0,r = 0;
		for(int i=0;i<a.length;i++){//对排序好的L、R数组进行排序
			if(L[l]>R[r]){
				a[i]=R[r];
				r++;
				if(r==rightLength){
					for(int j=i+1;j<a.length;j++){
						a[j]=L[l];
						l++;
					}
					break;
				}
			}else{
				a[i]=L[l];
				l++;
				if(l==leftLength){
					for(int j=i+1;j<a.length;j++){
						a[j]=R[r];
						r++;
					}
					break;
				}
			}
		}
	}
	/**
	 * 归并排序进行降序排序,不使用哨兵
	 * @param a待排序数组
	 */
	private static void sortDesc(int[] a){
		if (a.length<=1){
			return ;
		}
		int leftLength = a.length/2;//左边部分的数组长度
		int rightLength = a.length-a.length/2;//右边部分的数组长度
		int L[] = new int[leftLength];
		int R[] = new int[rightLength];
		for(int i = 0;i<leftLength;i++){//赋值
			L[i]=a[i];
		}
		for(int i = 0;i<rightLength;i++){//赋值
			R[i] = a[leftLength+i];
		}
		sortDesc(L);//分治左边数组
		sortDesc(R);//分治右边数组
		int l = 0,r = 0;
		for(int i=0;i<a.length;i++){//对排序好的L、R数组进行排序
			if(L[l]<R[r]){
				a[i]=R[r];
				r++;
				if(r==rightLength){
					for(int j=i+1;j<a.length;j++){
						a[j]=L[l];
						l++;
					}
					break;
				}
			}else{
				a[i]=L[l];
				l++;
				if(l==leftLength){
					for(int j=i+1;j<a.length;j++){
						a[j]=R[r];
						r++;
					}
					break;
				}
			}
		}
	}
	/***
	 * 
	 * @param a排序数组a,不使用哨兵,升序
	 */
	public static void sort(int[] a){
		sortAsc(a);
	}
	/***
	 * @param a排序数组a,不使用哨兵
	 * @param orderBy 值为desc为降序,否则为升序
	 */
	public static void sort(int[] a,String orderBy){
		if(orderBy!=null && orderBy.toLowerCase().equals("desc")){
			sortDesc(a);
		}else{
			sortAsc(a);
		}
	}
	public static void main(String[] args) {
		int k=10;
		while(k>0){
			int a[] = MRand.getRandInt(10000);
			int b[] = MRand.getRandInt(1000000);
			int c[] = MRand.getRandInt(10000);
			MTime.setStartTime();
			MTime.getDValue("开始");
			sort(a);
			MTime.getDValue("1万条数据,归并排序耗时:");
			sort(b);
			MTime.getDValue("100万条数据,归并排序耗时:");
			InsertSort.sort(c);
			MTime.getDValue("1万条数据,插入排序耗时:");
			k--;
		}
	}
}
在和上一节的插入排序进行速度的比较结果如下:

开始0
1万条数据,归并排序耗时:5
100万条数据,归并排序耗时:188
1万条数据,插入排序耗时:55
开始0
1万条数据,归并排序耗时:1
100万条数据,归并排序耗时:158
1万条数据,插入排序耗时:34
开始0
1万条数据,归并排序耗时:2
100万条数据,归并排序耗时:150
1万条数据,插入排序耗时:11
开始0
1万条数据,归并排序耗时:1
100万条数据,归并排序耗时:164
1万条数据,插入排序耗时:10
分治算法是用空间换来时间的算法,递归很消耗内存。


此外在待排序的数字值小于某个数时,插入排序性能将由于归并排序,所以可以将归并排序改进,将叶节点变粗,即在问题缩小到一定规模时(而不是不可划分),使用插入排序,再返回排序好的数组,使用归并排序的合并机制来合并数组。

时间复杂度分析:对于归并排序,每次将问题分解成两个小问题,即T(n)=2T(n/2)+D(n)+C(n),其中D(n)表示将分解子数组的时间,C(n)表示合并子数组的时间,D(n)+C(n)=R(n)。问题规模为n,则树深log2 n,则根据主定理证明,T(n)=nlog2 n。


此外对于基本排序好的数列,使用插入排序的速度优于归并排序。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值