归并思想的经典算法---归并排序

上一篇博客简单讲了一下分而治之的快速排序,那这篇讲一下另一种重要的算法思想归并思想,搬出了归并思想的经典算法示例归并排序

一、思路分析

归并与分治的不同点在于核心不同,分治的核心在于分,而归并的核心在于合并,归并不管你这一刀往哪一划,我只在最终结果的合并把你给排好。

什么是归并排序
思路:归并排序重在最后的合并,划分直接中间切一刀就可以了。
第一步:以中间下标分开左右两数组
第二步:分别调用递归归并排序
重点
合并步骤:
想象有两条排好序的队伍,要通过一个门,一次过一个。
也就是每条队伍的第一个互相比较,谁小谁先过,总有一条队伍的头部会改变。
要解决的问题是,过去了的元素放哪。
解决方法:调用System.arraycopy(arr, p, helper, p, r-p+1);拷贝进来的原数组。
由于是递归调用,那么每次进来要排序的数组的长度都不一样,所以拷贝的长度使用 r-p+1来控制
例如,左边进来5个元素成为一个数组,在原数组中下标是0,1,2,3,4
拷贝的数量:4-0+1 = 5;
右边进来4个元素成为一个数组,在原数组中下标是5,6,7,8
拷贝的数量:8-5+1 =4;
但是长度肯定不会超过arr.length。因为每次递归调用都是上一次的一半或者一半+1进来

实现步骤
第一步:定义三个指针
left、arrIndex 初始都指向进来的数组的左边的头部,也就是p
right指向进来的数组右边的头部,也就是mid+1;
要注意的是,arrIndex记录的是原数组的下标指向,并非辅助空间数组的

第二步:进入循环,循环结束的条件是左边的数组走完了,或者右边的数组走完了
判断:辅助数组left 与 辅助数组 right 所指向的元素谁小,谁小谁现在走
走去哪?
去覆盖掉原数组arrIndex所指向的位置的元素
然后
arrIndex ++;
left/right ++;
无论辅助数组中的left、right元素谁走,
arrIndex肯定都是要走的,
不然会覆盖掉上一次排好序的元素

第三步:循环结束后
循环结束时,要么left走到头了,要么是right走到头了,要么大家都走到头了。
后面两种不需要特别处理,
第三种不用过多解释,
第二种情况下,right剩下没到头的元素肯定是比前面都要大的,直接不处理就行了,因为在原数组中,就已经是在自己的位置上了。
但是如果是第一种情况呢?right走完了,但是left还没走完,
这种情况就需要另外再进行一次循环,将左边剩下的元素覆盖到原数组的最右边
怎么放呢?
当循环结束的时候,arrIndex前面的元素都是已经排序好了的。
当前指向的肯定是没有被覆盖过的那个。因为每次覆盖后,arrIndex都会+1
所以,此时再进行一次判断循环
当left<=mid的时候,就说明左边没走完
然后将辅助数组中lefe所指向的元素去覆盖掉原数组arrIndex所指向的位置的元素
然后arrIndex、left都要++,继续判断一下还有没有剩下的元素
如果没有了,就退出循环,传进来的原数组就排好序了。

还有一点要弄明白
在代码:

public static void sort(int[] arr, int p, int r) {
		if (p < r) {
			int midIndex = p + ((r-p)>>1);
			// 两边分别归并排序
			sort(arr, p, midIndex);
			sort(arr, midIndex + 1, r);
			// 归并排序
			merge(arr, p, midIndex, r);
		}

递归调用了归并排序的算法,原理就是不断的将数组一半一半的切开,进行归并排序
每次规模都会减少1/2,这个跟二分查找有相似之处,复杂度为O(lgn)

二、代码实现

// 归并排序
public class Demo33 {
	static int[] helper;
	public static void main (String[] args) {
		int[] arr = Util.RandomIntArr(10, 1, 20);
		Util.printlnArr(arr);
		helper = new int[arr.length];
		sort(arr, 0, arr.length-1);
		Util.printlnArr(arr);
	}
	
	public static void sort(int[] arr, int p, int r) {
		
		if (p < r) {
			int midIndex = p + ((r-p)>>1);
			// 两边分别归并排序
			sort(arr, p, midIndex);
			sort(arr, midIndex + 1, r);
			// 归并排序
			merge(arr, p, midIndex, r);
		}
	}
	
	public static void merge(int[] arr, int p, int mid, int r) {
		// 拷贝数组
		System.arraycopy(arr, p, helper, p, r-p+1);
		// 初始化三指针:left的头指针, right的头指针, 原数组的指针
		int left = p; // 左边的头指针
		int right = mid+1; // 右边的头指针
		int arrIndex = p;  // 指向原数组的当前位置
		// 进入循环
		while (left <= mid && right <= r) {
			// 在辅助空间中比对两个值的大小
			if (helper[left] < helper[right]) {
				arr[arrIndex] = helper[left];  // 将小的值替换掉原数组所在位置的值
				left ++;
				arrIndex ++;
			}else {
				arr[arrIndex] = helper[right]; // 将小的值替换掉原数组所在位置的值
				right ++;
				arrIndex ++;
			}
		}
		// 当右边的走完了,左边还没走完
		while (left <= mid) {
			arr[arrIndex] = helper[left];
			left ++;
			arrIndex ++;
		}
	}
}

运行效果
在这里插入图片描述

三、强烈建议

看不懂的强烈建议结合前面的思路分析和代码一起看,动手敲,敲2次应该就能够理解了。敲3次肯定能记住了,敲5次就肯定有自己的理解方法了。
手上功夫,别做一个走马观花的人。

原创文章 16 获赞 4 访问量 1552

猜你喜欢

转载自blog.csdn.net/weixin_44702572/article/details/104580145
今日推荐