一.合并两个已经排序的数组
要搞清楚什么是归并排序(MergeSort),我们首先要搞清楚一个基本操作:合并两个已经排序的数组。
因为两个数组A、B是排序好的,所以,我们可以定义两个指针,初始化的时候两个指针分别指向A、B的第一个元素,然后比较两个指针指向的元素。比较得出较小的元素放在第三个空数组中,然后该元素所在的数组的指针下移一位,之后继续比较。直到某个数组都被放在了第三个数组,此时将另一个数组剩余的元素都放入到第三个数组。
我们用图解来表述这一过程。例如,我们合并[8, 14, 56, 99],[10, 11, 34, 40]这两个数组。
1.定义两个指针,初始化时分别指向两个数组的第一个元素。
2.比较两个指针指向的元素,将较小的元素放入第三个数组,然后该元素指针下移一位。
3.重复步骤2,直到某一个数组都被放入到第三个数组。此时将另一个数组剩下的元素依次放入到第三个数组。
二.什么是归并排序?
搞清楚合并两个已经排序的数组后,我们再来看归并排序,其实归并排序的基本操作就是合并两个已排序的表。归并排序采用经典的分而治之(divide-and-conquer)策略,它将问题分(divide)成一些小的问题然后递归求解,而治(conquer)则是分阶段解得各答案并修补在一起。
如图所示,我们对[5, 2, 9, 0, 1, 7, 8, 4]进行归并排序。
三.代码实现
MergeSort.java
public class MergeSort {
/**
* 归并排序,对外暴露的方法
* @param a 准备被排序的数组
* @param <T>
*/
public static <T extends Comparable<? super T>> void mergeSort(T[] a) {
T[] tmpArr = (T[]) new Comparable[a.length];
mergeSort(a, tmpArr, 0, a.length - 1);
}
/**
* 归并排序
* @param a 原数组
* @param tmpArr 临时数组
* @param leftPos 数组第一个元素索引
* @param rightPos 数组最后一个元素索引
* @param <T>
*/
private static <T extends Comparable<? super T>>
void mergeSort(T[] a, T[] tmpArr, int leftPos, int rightPos) {
if (leftPos < rightPos) {
// 分
int centerPos = (leftPos + rightPos) / 2;
mergeSort(a, tmpArr, leftPos, centerPos);
mergeSort(a, tmpArr, centerPos + 1, rightPos);
// 治
merge(a, tmpArr, leftPos, centerPos + 1, rightPos);
}
}
/**
* 合并两个数组
* @param a 原数组
* @param tmpArr 临时数组
* @param leftPos 左数组第一个元素索引
* @param rightPos 右数组第一个元素索引
* @param rightEnd 右数组最后一个元素索引
* @param <T>
*/
private static <T extends Comparable<? super T>>
void merge(T[] a, T[] tmpArr, int leftPos, int rightPos, int rightEnd) {
// 左方数组末位元素索引
int leftEnd = rightPos - 1;
// 临时指针为左数组第一个元素索引
int tmpPos = leftPos;
// 整个数组元素个数
int numElements = rightEnd - leftPos + 1;
// 当两个子数组都没有遍历完前,依次对比两个数组的元素
while (leftPos <= leftEnd && rightPos <= rightEnd) {
if (a[leftPos].compareTo(a[rightPos]) <= 0) {
tmpArr[tmpPos++] = a[leftPos++];
} else {
tmpArr[tmpPos++] = a[rightPos++];
}
}
// 将左边数组剩余元素填入tmp中
while (leftPos <= leftEnd) {
tmpArr[tmpPos++] = a[leftPos++];
}
// 将右边数组剩余元素填入tmp中
while (rightPos <= rightEnd) {
tmpArr[tmpPos++] = a[rightPos++];
}
// 将tmp中的数组拷贝回原数组
for (int i = 0; i < numElements; i++, rightEnd--) {
a[rightEnd] = tmpArr[rightEnd];
}
}
}
MergeSortTest.java测试类
public class MergeSortTest {
public static void main(String[] args) {
Integer[] arr = {46, 99, 32, 12, 14, 9, 53, 20};
MergeSort.mergeSort(arr);
print(arr);
}
private static <T> void print(T[] arr) {
System.out.print("归并排序后的结果为: ");
for (T item : arr) {
System.out.print(item.toString() + " ");
}
}
}
输出:
归并排序后的结果为: 9 12 14 20 32 46 53 99
四.有关归并排序的复杂度
归并排序每次合并操作的平均时间复杂度为O(N),而完全二叉树的深度为logN,所以总的平均时间复杂度为O(NlogN)。而且,归并排序的最好、最坏、平均时间复杂度均为O(NlogN)。
有关[数据结构与算法]的学习内容已经上传到github,喜欢的朋友可以支持一下。 data-structures-and-algorithm-study-notes-java
站在前人的肩膀上前行,感谢以下博客及文献的支持。
- 《数据结构与算法分析(第3 版) 工业出版社》
- 图解排序算法(四)之归并排序