简单理解-归并排序

145 阅读2分钟

归并排序思想-源码

注:本文内容参考左程云算法,需要的可以去自行搜索学习,我只是做学习记录并分享给大家

什么是归并排序呢?

简单的理解就是,给定一个数组。我们可以把他分成两半,先让左半部分有序,再让右半部分有序,最终归并起来,再次排序,那么整个数组就都有序。

我知道看到这里,你可能有些不了解,继续往下看吧。我将分递归版和非递归版来说明

递归版

由于代码比较简单,我们就直接开写,也写了详细的介绍。

	// 递归方法实现
	public static void mergeSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process(arr, 0, arr.length - 1);
	}

	/**
     * 过程就是,先求出 L和R的中点
     * 1):先让[L,mid]有序
     * 2):再让[mid+1,R]有序
     * 3):merge(arr,L,mid,R)就算让,将左右两部分合并起来,让整体有序
     * 
     * 这是一个黑盒过程,具体排序过程在merge方法里面
     * @param arr  数组
     * @param L    左边界
     * @param R    右边界
     */
	public static void process(int[] arr, int L, int R) {
		if (L == R) { // base case
			return;
		}
		int mid = L + ((R - L) >> 1);
		process(arr, L, mid);
		process(arr, mid + 1, R);
		merge(arr, L, mid, R);
	}

	/**
     * merge过程可以结合代码和下方的图片理解
     * 
     * @param arr 数组
     * @param L   左边界
     * @param M   中点
     * @param R   右边界
     */
    private static void merge(int[] arr, int L, int M, int R) {
        //定义辅助数组help
        int[] help=new int[R-L+1];
        /**
         * 下面三个变量是指针:
         *  i-是help的下标索引
         *  p1-左边界下标索引
         *  p2-右边界下标索引
         */
        int i=0;
        int p1=L;
        int p2=M+1;
        //在p1和p2都合法的范围内,谁小取谁,知道左右边界有一边越界为止
        while(p1<=M && p2<=R){
            help[i++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
        }
        //要么p1越界,要么p2越界
        while(p1<=M){
            help[i++]=arr[p1++];
        }
        while(p2<=R){
            help[i++]=arr[p2++];
        }
        //将原数组重新赋值
        for(i=0;i<help.length;i++){
            arr[L+i]=help[i];
        }
    }
	}
  • merge过程图解

注意的是,每次merge的时候,左右两边都是已经排好序的了。

image.png

非递归版

相信你一定能够很容易理解上面的写法,不过可能你还是不能理解为什么 归并排序的时间复杂度是O(logN*N),接下来我们从非递归版的写法中就可以很容易理解了

  • 迭代版的代码思想和递归版的差不多。

递归版是从大到小,一层一层排序完,再往回merge合并排序。
迭代版是根据步长,从小到大,一层一层merge。
image.png
现在可以就清楚知道为什么时间复杂度是O(NlogN)了,因为每次merge都是不回退的,那就是O(N);
而步长是 2倍2倍的变化,那就是O(logN),所以合起来就是O(N
logN)了

具体代码如下,有详细的注释

public static void mergeSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		int N = arr.length;
		// 步长
		int mergeSize = 1;
		while (mergeSize < N) { // log N
			//当前左组的,第一个位置
			int L = 0;
			//当左组超过,数组长度时,当前轮结束
			while (L < N) {
				//当发现这组的左边界小于步长时,此轮merge结束
				if (mergeSize >= N - L) {
					break;
				}
				//中点
				int M = L + mergeSize - 1;
				//若右边界小于步长,就以实际长度
				int R = M + Math.min(mergeSize, N - M - 1);
				//merge
				merge(arr, L, M, R);
				L = R + 1;
			}
			// 防止溢出,如果不了解为什么,可以看下面的图
			if (mergeSize > N / 2) {
				break;
			}
			mergeSize <<= 1;
		}
}
  • 步长越界

image.png

  • 过几天我会写一些过于归并排序实际应用的题目,感兴趣的小伙伴可以关注一下