常考排序算法之归并排序
Posted 飞人01_01
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常考排序算法之归并排序相关的知识,希望对你有一定的参考价值。
前几天,我发布了一篇快速排序的帖子,今天我们还是围绕着排序这个话题,继续讲解第二热度的归并排序
。归并的递归版本非常的简单,非递归版本的可能稍微要难一点点,但是,不要怕,这都是小问题。我们直接开干!!!
前期文章:
一、递归版本
归并排序的思想:将给定的数据,等分为两部分,分别递归调用函数,使这两部分有序。然后再将这左右两部分的数据进行合并,使其有序。这就是归并。
如上图,递归调用绿色部分,使其有序后,再调用蓝色部分,使其有序后;再将二者进行合并,且还是有序的合并。
所以,在传入进来的数组,首先就是进行mid的计算,然后调用左部分,再调用右部分。最后调用merge方法进行合并。
//伪代码
public void mergeSort(int[] array, int start, int end) {
int mid = start + (end - start) / 2; //计算中间值
mergeSort(array, start, mid); //左部分
mergeSort(array, mid + 1, end); //右部分
调用merge方法进行合并
}
大致框架知道后,现在就是merge方法的实现了,假设我们需要将给定的数据都排升序。则有如下思路:
-
首先开辟一个数组,大小为end - start + 1,用于临时存储已经有序的数据。
-
然后开始拿取左部分和右部分的数据,进行比较。
- 假设左部分的值小,左部分的值就先放入临时数组,然后取出下一个数值
- 假设右部分的值小,右部分的值就先放入临时数组,然后取出下一个数值
-
直到左部分遍历结束,或者右部分遍历结束,循环停止
上面3点过后,循环停止下来,肯定是因为左部分的数据遍历完了,或者是右部分的数据遍历完了。此时肯定有一部分的值还没有放入临时数组,所以还需将没放入临时数组的所有数据,都放入进去。
最后将临时数组的所有数据,都放回array原数组即可。
public void merge(int[] array, int left, int mid, int right) {
int[] tmp = new int[right - left + 1];
int p1 = left; //指向左部分数组的下标
int p2 = mid + 1; //指向右部分数组的下标
int index = 0; //指向临时数组的下标
while (p1 <= mid && p2 <= right) {
if (array[p1] <= array[p2]) {
tmp[index] = array[p1];
p1++;
} else {
tmp[index] = array[p2];
p2++;
}
index++; //下标后移
}
while (p1 <= mid) { //左部分的数据还没遍历完
tmp[index] = array[p1];
index++;
p1++;
}
while (p2 <= right) { //右部分的数据还没遍历完
tmp[index] = array[p2];
index++;
p2++;
}
//将临时数组的所有数据,放回array数组
for (int i = left; i <= right; i++) {
array[i] = tmp[i];
}
}
以上的代码,就是merge方法,还算是比较简单的,就是谁的数值更小,谁就先放入临时数组即可。
主方法代码:
public void mergeSort(int[] array, int start, int end) {
if (start >= end) return;
int mid = start + (end - start) / 2;
mergeSort(array, start, mid);
mergeSort(array, mid + 1, end);
merge(array, start, mid, end);//调用合并方法
}
二、非递归版本
递归版本的代码,是从顶至下
进行拆分,比如:待排序的数组的长度是10个,我们可以拆分为5个数据一组,使其有序;而这5个数据,我们还可以拆分为2个数据和3个数据为……,一直往下拆分。
而非递归版本的代码,是从底向上
。也就是说,第一次,我们拆分为左部分一个数据,右部分一个数据,然后合并;然后再以下一组,左部分一个数据,右部分一个数据,再合并……如下图:
如上图,我们先让每一个小组进行有序后,再调用下一趟,使组的数据量扩大一倍即可。
public void mergeSortNoRecur(int[] array) {
int N = array.length;
int leftPart = 1; //左部分的元素个数
while (leftPart < N) {
int start = 0; //左边界值
while (start < N) {
int mid = start + leftPart - 1; //中间值
int end = mid + leftPart; //右边界值
if (mid > N) break; //中间值,超过数组长度,直接跳出
int newEnd = Math.min(end, N); //新右边界值,与N取最小值。作为这一组的右边界
merge(array, start, mid, newEnd); //调用merge方法
start = end + 1; //进入下一组
}
leftPart *= 2; //扩大每一个的数据量
}
}
以上代码,就是非递归版本的代码,最外层的while循环是躺数,里面的while循环是用于合并这一趟中各个小组的数据。
归并的时间复杂度是O(N logN),空间复杂度是O(N)。
时间复杂度的推导:每次将数组进行二分,实则拆分下来就是一颗二叉树,高度就会logN,再对每个节点进行合并,即就是O(N logN)。空间复杂度的推导:每个合并的时候,都会开辟一个数组,而则这个数组的长度最大的时候,就是递归版本代码中,最后一次的合并操作,也就是第一次拆分为左右部分的时候。即就是O(N)。
好啦,本期更新就到此结束啦!我们下期见!!!
以上是关于常考排序算法之归并排序的主要内容,如果未能解决你的问题,请参考以下文章