归并排序
1.定义:
(1)归并操作:将两个有序的数组归并成一个更大的有序数组。
(2)通过递归的进行归并操作就是归并排序。
(3)归并排序又分为自顶向下的归并排序和自底向上的归并排序。
2.实现归并操作:
代码剖析:当我想要将两个有序的数组归并成一个更大的有序数组时。
首先,我们需要一个空的辅助数组来达到更好的存取值的效果;
接着,比较前一个有序数组的当前元素与后一个有序数组的当前元素的大小,小的则放入辅助数组中;
最后,当判断到前一个数组已经用尽,则取后一个数组的元素,反之亦然。
实现两个有序数组归并的代码如下:
//辅助数组
private static int[] aux;
//归并两个有序数组
private static void merge(int[] a, int low, int middle, int high) {
int i = low;
int j = middle + 1;
//将数组中的数组复制到辅助数组中
aux = Arrays.copyOf(a, a.length);
for (int k = low; k <= high; k++) {
//通过四个条件判断来对数组进行合并
//1.左半边用尽(取右半边的元素)
//2.右半边用尽(去左半边的元素)
//3.右半边的当前元素小于左半边的当前元素(取右半边的元素)
//4.右半边的当前元素大于左半边的当前元素(取左半边的元素)
if (i > middle) {
a[k] = aux[j++];
} else if (j > high) {
a[k] = aux[i++];
} else if (aux[j] < aux[i]) {
a[k] = aux[j++];
} else {
a[k] = aux[i++];
}
}
}
将两个有序数组的实现已经知道,那么,如何将一个无序数组进行归并排序呢?有两个实现方法,分别为自顶向下的归并排序以及自底向上的归并排序,下面我将一一讲解。
(1)自顶向下的归并排序
1.思想:当我们要归并一个数组的时候,我们将这个数组分成前后两部分,分别归并它的子数组;然后子数们各自又分成前后两部分进行归并;然后迭代下去,直到最后分成只剩一个元素,于是开始俩俩归并。自顶向上的的归并排序的关键是递归。这也是分治思想的一个典型的例子。
2.代码:
/**
* 自顶向下的归并排序
* 排序过程:
* 1.首先我们获得数组的中间索引。
* 2.获得中间索引后,将我们的数组分成两边,分别处理
* 3.对子数组再分成两部分,再对其子数组进行处理,这样递归下去,直到子数组只剩下一个元素就结束
* 4.最后各个子数组调用归并进行两两有序数组的归并排序
* @param a
*/
public static void mergeSort(int[] a) {
aux = new int[a.length];
sort(a, 0, a.length - 1);
}
private static void sort(int[] a, int low, int high) {
//将数组按a[low...high]排序
if (high <= low) {
return;
}
int middle = low + (high - low) / 2;
sort(a, low, middle);
sort(a, middle + 1, high);
if (a[middle] > a[middle + 1]) {
merge(a, low, middle, high);
}
}
3.图解:
(2)自底向上的归并排序
1.思想:自底向上的归并排序时先归并完那些所有的微型数组,然后再成对归并得到的子数组,再归并所有的子数组,如此这般,直到我们将整个数组归并在一起。首先我们进行的是两两归并(把每个元素想象成一个大小为1的数组),然后是四四归并(将两个大小为2的数组归并成一个有四个元素的数组),然后是八八归并,一直下去。
2.代码:
/**
* 自底向上的归并排序
* 排序过程:
* 1.设定一个参数,作为每次要归并的数组的大小
* 2.在归并完一次后,将大小翻倍
* 3.最后一次归并的第二个数组的大小可能小于要归并的数组的大小,因此需要取出两者间的最小值
* @param a
*/
public static void mergeSort2(int[] a) {
int N = a.length;
aux = new int[N];
for (int sz = 1; sz < N; sz = sz + sz) {
for (int low = 0; low < N - sz; low += sz + sz) {
merge(a, low, low + sz - 1, Math.min(low + sz + sz - 1, N - 1));
}
}
}
3.图解:
3.算法优劣分析:
(1)在归并排序下,任意长度为N的数组排序所需时间和NlogN成正比。
(2)主要缺点:它所需要的额外空间(即空间复杂度)和数组长度N成正比。
(3)当数组长度为2的幂时,自顶向下和自底向上的归并排序所用的比较次数和数组访问次数相同,只是它们的排序先后顺序不同。
(4)自底向上的归并排序比较适合用链表组织的数据。因为这种方法只需重新组织链表链接就能将链表原地排序(不需要创建任何新的链表结点)
4.资料引用:
《算法Algorithms(第4版)》 p170~p179