归并排序
基本思想
归并排序其实是分治思想的体现,先解决小规模的问题,小规模的问题解决后,大的问题自然就会得到解决。
例如对于长度为15的数组a,先将子数组a[0-7]排序,再将a[8-14]排序,这两个子数组排序后归并结果即可。整个过程其实可以用一个二叉树来表示:
图1 归并排序示意图
归并两个已经排序的子数组的过程中,需要将这两个子数组的值复制到另一个临时数组中,再从小到大归并到原数组。
代码实现
代码实现如下:
/**
* <p>文件描述: 归并排序</p>
*
* @Author luanmousheng
* @Date 17/8/1 下午2:46
*/
public class MergeSort {
//归并时用到的临时空间
private static int[] temp;
public static void sort(int[] arr) {
if (arr == null) {
return;
}
sort(arr, 0, arr.length - 1);
}
private static void sort(int[] arr, int lo, int hi) {
//如果数组开始位置大于等于结束位置,说明本次排序完成,返回
if (lo >= hi) {
return;
}
//取中间位置
int mid = (lo + hi) /2;
//将左边的部分排序
sort(arr, lo, mid);
//将右边的部分排序
sort(arr, mid+1, hi);
//此时两边已经是排序的,归并结果
merge(arr, lo, mid, hi);
}
private static void merge(int[] arr, int lo, int mid, int hi) {
//先将原数组lo到hi处的值复制到临时数组
for (int i = lo; i <= hi; i++) {
temp[i] = arr[i];
}
//i是左边数组的开始位置
int i = lo;
//j是右边数组的开始位置
int j = mid+1;
//遍历原数组lo到hi位置,
for (int k = lo; k <= hi; k++) {
if (i > mid) {
//左边部分已经遍历完
arr[k] = temp[j++];
} else if (j > hi) {
//右边部分已经遍历完
arr[k] = temp[i++];
} else if (temp[i] < temp[j]) {
//取较小的值放到原来数组的位置k处
arr[k] = temp[i++];
} else {
arr[k] = temp[j++];
}
}
}
public static void main(String[] args) {
int[] arr = {4,3,5,8,11,9,2,7};
temp = new int[arr.length];
sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + ", ");
}
}
}
这里的逻辑就是先递归排序左边的数组,然后递归排序右边的数组,接着归并这两个已经排序完成的数组。
图2 两个有序数组的归并
图2 归并时会将原数组的数据复制到临时数组,临时数组上放上两个指针i和j,分别指向左边数组和右边数组的开始位置,比较这两个位置的值并将较小的值存储到原数组,直到这两个子数组遍历完成。
复杂度
归并排序的空间复杂度为O(n),时间复杂度为O(nlogn)。