数据结构与算法---排序---归并排序

归并排序(Merge Sort)

1945年由冯诺依曼首次提出。

执行流程

1 不断的将当前序列平均分割成两个子序列
直到不能再分割(序列中只剩1个元素)

2 不断的将两个子序列合并成一个有序序列
直到最终只剩下1个有序序列

在这里插入图片描述

归并排序,主要有分和和两个步骤。
其中,分感觉好理解些,找出begin,end,求出middle,然后递归调用,直到元素个数小于或等于1时,不再分割。不难写出:

public static void divideSort(int[] array, int begin, int end)
	{
		if (end - begin <= 1) return;
		int middle = (begin + end)/2;
		
		//假如[9, 18, 67, 99, 116, 156, 8]
		//[begin, middle) = [0, 3)
		divideSort(array, begin, middle);
		//[middle, end) = [3, 6)
		divideSort(array, middle, end);
	}

合并就有些许难度,这其实算是另外一道算法题:

如何将两个有序数组合并成一个大数组?

比如,我们需要将两个有序数组[3, 8]、[6, 10]合并成一个大数组。

在这里插入图片描述

大致流程是:
  1. 建立一个可以容纳两个数组长度的大数组array,用以存放排序好的数组。并设立一个变量ai,用以存放新元素要存放的位置。默认是0;

  2. 设立两个变量lefti、righti,分别指向两个数组当前比较到了哪里。默认都是0,也就是li = 0; ri = 0;

  3. 比较array1[li] 与array2[ri]值的大小

    如果array1[li] <= array2[ri],则将array[ai] = array1[li]; li++;
    如果li超过array1的边界,则将array2剩余的元素依次放在新数组array后面。

    如果array1[li] > array2[ri],则将array[ai] = array2[ri]; ri++;
    如果ri超过array2的边界,则将array1剩余的元素依次放在新数组array后面。

有了以上思路,不难写出代码:

public static void main(String[] args)
	{
		int[] array1 = {1, 2, 3};
		int[] array2 = {2, 4, 6, 10};

		mergeSortArray(array1, array2);
	}
	
public static void mergeSortArray(int[] array1, int[] array2)
	{
		int li = 0;
		int ri = 0;
		int ai = 0;
		
		int[] array = new int[array1.length + array2.length];
		
		while ((li < array1.length) && (ri < array2.length)) {
			if (array1[li] <= array2[ri]) {
				array[ai++] = array1[li++];
			}else {
				array[ai++] = array2[ri++];
			}
		}
		
		if (li == array1.length) {
			for (int i = ri; i < array2.length; i++) {
				array[ai++] = array2[ri++];
			}
		}
		
		if (ri == array2.length) {
			for (int i = li; i < array1.length; i++) {
				array[ai++] = array1[li++];
			}
		}
		

		for (int i = 0; i < array.length; i++) {
			System.out.print(array[i]+ " ");
		}
	}

打印结果:
1 2 2 3 4 6 10 

写完了两个不相关的数组,我们再看一下归并排序的合并

归并排序的两个数组是相连的,并且数组大小是等分的,因此,在建立新的数组时候,不需要建立一个等长的大数组。
只需要将前面一个数组copy出一份,然后将新copy出来的数组,与后面的数组做对比,将比较出来的元素放入到原数组中即可。这样,空间复杂度比上面的少一半。

在这里插入图片描述

左边先结束,什么也不做
右边先结束,左边按个移过来

public static void main(String[] args)
	{
		int[] array = {1, 2, 3, 2, 4, 6, 10};
		divideSort(array, 0, array.length);
		
		for (int i = 0; i < array.length; i++) {
			System.out.print(array[i]+ " ");
		}
	}
	
	public static void divideSort(int[] array, int begin, int end)
	{
		if (end - begin <= 1) return;
		int middle = (begin + end)/2;
		
		//假如[9, 18, 67, 99, 116, 156, 8]
		//[begin, middle) = [0, 3)
		divideSort(array, begin, middle);
		//[middle, end) = [3, 6)
		divideSort(array, middle, end);
		
		mergeSort(array, begin, middle, end);
	}
	
	
	public static void mergeSort(int[] array, int begin, int middle, int end)
	{
		int li = begin, le = middle - begin;//左边数组
		int ri = middle, re = end;//右边数组
		int ai = begin;//array需要插入的位置
		
		//copy左边的数组
		int[] leftArray = new int[middle];
		for (int i = 0; i < le; i++) {
			//注意右边是begin + i
			leftArray[i] = array[begin + i];
		}
		
		//如果左边还没结束
		while (li < le) {
			if (ri < re) {
				if (leftArray[li] <= array[ri]) {//左边数组小于或等于大数组右边的值
					array[ai++] = leftArray[li++];
				}else {
					array[ai++] = array[ri++];
				}
			}else {//右边结束,将leftArray依次移过来
				array[ai++] = array[li++];
			}
		}
		//如果左边先结束,则剩下的啥也不需要做了
		
	}

其中,里面的while循环可优化:

while (li < le) {
	if (ri < re && (leftArray[li] > array[ri])) {
		array[ai++] = array[li++];
	}else {//右边结束,将leftArray依次移过来
		array[ai++] = leftArray[li++];
	}
}

归并排序复杂度分析

归并排序花费时间
假设归并排序花费的时间为T(n)
里面有两个均分的归并排序T(n/2)
以及一个合并遍历O(n)

那么,可以得出:
T(n) = 2*T(n/2) + O(n);…1
并且T(1) = O(1);

那么1式左右同除以n
T(n)/n = 2*T(n/2)/n + O(1);
T(n)/n = T(n/2)*2/n + O(1);
T(n)/n = T(n/2)/n/2 + O(1);

令S(n) = T(n)/n;,那么S(1) = T(1) = O(1);
S(n) = S(n/2)+ O(1) = S(n/4)+ O(2) = S(n/8)+ O(3) = S(n/2^k)+ O(k)
n = 2^k,k = logn
S(n) = S(2^k) = S(1) + O(logn) = O(logn)
S(n) = T(n)/n = O(logn)
那么,T(n) = O(nlogn)

由于归并排序总是平均分割子序列,所以最好、最坏、平均时间复杂度都是O(nlogn),属于稳定排序。

归并排序的空间复杂度为:
在分割的时候,由于是递归调用,每次递归需要占用内存空间,递归调用分割logn次,也就是分割是O(logn)
在合并的时候,由于需要将左边的数组copy出一份,因此,占用空间是O(n/2)

所以,归并排序的空间复杂度为O(logn + n/2) 约等于 O(n)


常用的递推式与复杂度

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值