归并排序是一种稳定高效的排序,采用分治策略,将问题分解为一个个小的问题,然后解决
主要思想
- 先分解,再归并
- 一次归并只能处理两个有序序列,所以需要将序列不断划分为更小的序列
- 当递归到最小的时候,每个小序列里只有一个元素,一定是有序的,所以可以进行归并
- 然后,因为这个归并之后的序列也是有序的了,所以可以和其它有序序列再进行归并,直到结束所有递归。
其中每次如何将两个有序序列进行归并:
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
- 重复步骤3直到某一指针到达序列尾
- 将另一序列剩下的所有元素直接复制到合并序列尾
即可完成一次二路归并
(两个有序序列的归并和合并两个有序链表的思想很像)
动画演示
Java代码
package algorithm;
import java.util.Arrays;
public class Sort {
// 归并排序
private static void mergeSort(int[] array, int low, int high) {
int mid = (high + low) / 2;
if (low >= high) {
return;
}
mergeSort(array, low, mid); // 递归前面的
mergeSort(array, mid +1, high); // 递归后面的
merge(array, low, mid, high); // 归并
}
// 归并
private static void merge(int[] array, int low, int mid, int high) {
// 创建一个临时数组,用于存放归并之后的结果
int temp[] = new int[high - low + 1];
int index = 0; // 临时数组的索引
int i = low; // 前面数组的索引
int j = mid + 1; // 后面数组的索引
while (i <= mid && j <= high) {
if (array[i] <= array[j]) {
temp[index] = array[i];
i++;
index++;
} else {
temp[index] = array[j];
j++;
index++;
}
}
// 将剩余的数组直接放入临时数组
while (j <= high) {
temp[index] = array[j];
j++;
index++;
}
while (i <= mid) {
temp[index] = array[i];
i++;
index++;
}
// 把临时数组存入原数组
System.arraycopy(temp, 0, array, low, temp.length);
}
public static void main(String[] args) {
int[] array = new int[]{5, 2, 0, 1, 3, 1, 4};
Sort.mergeSort(array, 0, array.length - 1);
System.out.println(Arrays.toString(array));
}
}
如上代码分析:
三个指针:
- low:前面序列的指针
- mid:两个序列的分界线
- high:后面序列的指针
结束条件:
- 因为不断递归划分,所以low、mid和hight指针在不断变化
- 但low应该始终在high的左面
- 所以,当low >= high的时候,就代表递归到最后一层,直接返回
递归:
- 先递归划分前面序列(每次都递归前面的序列,直到递归到最底层,深度优先)
- 然后当其中一个序列的前面序列递归返回后,我们就递归它后面的序列
- 当前面和后面都递归到最底层返回的时候(一定是一个有序序列),我们就将它们进行归并
归并(二路归并):
- 先创建一个临时数组temp,用于存放归并之后的序列
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置
- 比较两个指针所指向的元素,选择相对小的元素放入到 temp 中,并移动指针到下一位置
- 重复执行步骤3,直到其中一个指针达到序列尾部
- 最后将另一个序列剩余的元素,直接全部放入temp中
- 再将temp同步到原数组中即可(注意放置的位置)
重复上述操作,直到所有递归结束
时间复杂度
最好情况:O(nlogn)
平均情况:O(nlogn)
最坏情况:O(nlogn)