归并排序
要将一个数组排序,可以先(递归地)将他分成两半分别排序,然后将结果归并起来
归并排序能够保证将任意长度为 N N N 的数组排序所需 时间 和 N l o g 2 N Nlog_2N Nlog2N 成正比
主要缺点: 归并排序所需的 额外空间 和 N N N 成正比
自顶向下
对于长度为 N N N 的任意数组,自顶向下的归并排序需要 1 / 2 N l o g 2 N 1/2Nlog_2N 1/2Nlog2N 至 N l o g 2 N Nlog_2N Nlog2N 次比较
对于长度为 N N N 的任意数组,自顶向下的归并排序最多需要访问数组 6 N l o g 2 N 6Nlog_2N 6Nlog2N 次
public class Merge {
private static Comparable[] aux;
public static void sort(Comparable[] a) {
int length = a.length;
aux = new Comparable[length];
sort(a, 0, length - 1);
}
public static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) {
return;
}
int mid = lo + (hi - lo) / 2;
sort(a, lo, mid);
sort(a, mid + 1, hi);
merge(a, lo, mid, hi);
}
/**
* 原地归并的抽象方法; 该方法将所有元素复制到 aux[] 中,然后在归并回到 a[] 中。 方法在归并时(第二个 for 循环)进行了 4 个条件判断: 左半边用尽 -> 取右半边的元素 右半边用尽 -> 取左半边的元素
* 右半边的当前元素小于左半边当前元素 -> 取右半边的当前元素 右半边的当前元素大于等于左半边的当前元素 -> 取左半边的元素
*
* @param a
* 待排序数组
* @param lo
* 最小下标
* @param mid
* 中值下标
* @param hi
* 最大下标
*/
private static void merge(Comparable[] a, int lo, int mid, int hi) {
int i = lo;
int j = mid + 1;
// 将 a[lo...hi] 复制到 aux[lo...hi]
for (int k = lo; k <= hi; k++) {
aux[k] = a[k];
}
// 归并回到a[lo...hi]
for (int k = lo; k <= hi; k++) {
if (i > mid) {
a[k] = aux[j++];
} else if (j > hi) {
a[k] = aux[i++];
} else if (less(aux[j], aux[i])) {
a[k] = aux[j++];
} else {
a[k] = aux[i++];
}
}
}
public static boolean less(Comparable a, Comparable b) {
return a.compareTo(b) < 0;
}
}
自底向上
算法原理:先归并微型数组,在***成对***归并得到的子数组
自底向上的归并排序多次遍历整个数组,根据子数组大小进行两两归并。子数组大小 sz 的初始值为 1,每次***加倍***。最后一个子数组的大小只有在数组大小时 sz 的***偶数倍***的时候才会等于 sz (否则它会比 sz 小)。
对于长度为 N N N 的任意数组,自底向上的归并排序需要 1 / 2 N l o g 2 N 1/2Nlog_2N 1/2Nlog2N 至 N l o g 2 N Nlog_2N Nlog2N 次比较,最多访问数组 6 N l o g 2 N 6Nlog_2N 6Nlog2N 次。
当数组的长度为 2 的幂时,自顶向下和自底向上所用的比较次数和数组访问次数正好相同。
- 自顶向下:化整为零
- 自底向上:循序渐进
public class MergeBU {
private static Comparable[] aux;
public static void sort(Comparable[] a) {
int length = a.length;
aux = new Comparable[length];
for (int sz = 1; sz < length; sz = sz + sz) {
for (int lo = 0; lo < length - sz; lo += sz + sz) {
merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, length - 1));
}
}
}
/**
* 原地归并的抽象方法; 该方法将所有元素复制到 aux[] 中,然后在归并回到 a[] 中。 方法在归并时(第二个 for 循环)进行了 4 个条件判断: 左半边用尽 -> 取右半边的元素 右半边用尽 -> 取左半边的元素
* 右半边的当前元素小于左半边当前元素 -> 取右半边的当前元素 右半边的当前元素大于等于左半边的当前元素 -> 取左半边的元素
*
* @param a
* 待排序数组
* @param lo
* 最小下标
* @param mid
* 中值下标
* @param hi
* 最大下标
*/
private static void merge(Comparable[] a, int lo, int mid, int hi) {
int i = lo;
int j = mid + 1;
// 将 a[lo...hi] 复制到 aux[lo...hi]
for (int k = lo; k <= hi; k++) {
aux[k] = a[k];
}
// 归并回到a[lo...hi]
for (int k = lo; k <= hi; k++) {
if (i > mid) {
a[k] = aux[j++];
} else if (j > hi) {
a[k] = aux[i++];
} else if (less(aux[j], aux[i])) {
a[k] = aux[j++];
} else {
a[k] = aux[i++];
}
}
}
public static boolean less(Comparable a, Comparable b) {
return a.compareTo(b) < 0;
}
}
归并排序的意义
归并排序是一种渐进最优的基于***比较排序***的算法
理解:最坏情况下任意基于***比较排序***的算法所需要的比较次数最少都是 N l o g 2 N ~Nlog_2N Nlog2N
归并排序的最优性不是结束,也不代表实际应用中不需要考虑其他方法,因为:
- 归并排序的空间复杂度不是最优
- 实践中不一定会遇到最坏情况
- 除了比较,算法的其他操作(例如访问数组)也可能很重要
比较排序*的算法
理解:最坏情况下任意基于***比较排序***的算法所需要的比较次数最少都是 N l o g 2 N ~Nlog_2N Nlog2N
归并排序的最优性不是结束,也不代表实际应用中不需要考虑其他方法,因为:
- 归并排序的空间复杂度不是最优
- 实践中不一定会遇到最坏情况
- 除了比较,算法的其他操作(例如访问数组)也可能很重要
- 不进行比较也能将某些数组排序