“分治”策略
分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。在计算机科学中,分治法就是运用分治思想的一种很重要的算法。分治法是很多高效算法的基础,如排序算法(快速排序,归并排序)等。
通俗来说就是把一个大的方案,分解成一个一个小方案,再组织起来。
例如对100个元素排序,可以分解成 2 组50个元素的排序,再分解成 4 组25个元素的排序…最后分解成 50 组 2个元素的排序。最后再对已经有序的元素进行整合。
归并
如上所示,归并算法是基于“分治”策略。即:要将一个数组排序,先(递归)将它分成两半分别排序,然后将结果归并起来。
归并过程中,我们需要把两个已经有序(同一数组的前半部分和后半部分)的元素排列,归并成一个有序的元素排列。
// "归并" 部分
public static void main(String[] args) {
int[] a=new int[]{1,2,8,9, 3,4,5,7};
aux=new int[a.length];//初始化辅助数组
merge(a,0,a.length/2-1,a.length-1);
}
private static int[] aux;//需要辅助数组,用来作比较
private static void merge(int[] a, int lo, int mid, int hi) {
int i = lo;
int j = mid + 1;
for (int k = 0; k < a.length; k++) {//对aux[]赋值
aux[k]=a[k];
}
for (int k = lo; k <= hi; k++) {
if (i > mid) {//左半部分已经有序
a[k] = aux[j++];
}
else if (j > hi) {// 有半部分已经有序
a[k] = aux[i++];
}
else if (aux[i] < aux[j]) {//比较元素,放入数组a[],下标+1
a[k] = aux[i++];
} else {
a[k] = aux[j++];
}
}
}
上面的代码和图文即归并排序中的“归并”部分。将一个无序的数组从 分解 到 归并 直至成为一个有序的数组,基于归并思想,有两种实现方法,分别是自顶向下的归并排序和自底向上的归并排序。
自顶向下(递归)
递归实现的归并排序是算法设计中分治策略的典型应用。
自顶向下的实现方式和Android View的绘制机制的measure()
方法很相似,这里穿插一下知识点:
measure过程是后根遍历(DecorView最后setMeasureDiemension()),所以子控件的测量结果影响父控件的测量结果。
下面摘选《算法4》中的图片来加深印象。
要对子数组a[lo..hi]进行排序,先将它分为
a[lo..mid]
和a[mid+1..hi]
两部分,分别通过递归调用将它们单独排序,最后将有序的子数组归并为最终的排序结果。
public static void main(String[] args) {
int[] a = new int[] { 1, 2, 8, 9, 3, 4, 5, 7 };
sort(a);
}
private static int[] aux;// 需要辅助数组,用来作比较
private static void sort(int[] a) {
aux = new int[a.length];
sort(a, 0, a.length - 1);
}
private static void sort(int[] a, int lo, int hi) {
if (lo >= hi) {
return;
}
int mid = lo + (hi - lo) / 2;
sort(a, lo, mid);// 左半边排序,里面可能递归sort和merge方法
sort(a, mid + 1, hi);// 右半边排序
merge(a, lo, mid, hi);// 归并,merge方法如上
}
自底向上(遍历)
自顶向上 : 我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。尽管我们考虑的问题是归并两个大数组,实际上我们归并的数组大多数都非常小。
自底向上 : 实现归并排序的另一种方法是先归并那些微型数组,然后再成对归并得到的数组,如此这般,直到我们将整个数组归并在一次。
1.首先我们把每个元素想象成一个大小为 1 的数组。(两两归并)
2.然后将两个大小为2的数组归并成一个有4个元素的数组。(四四归并)
3.然后再将2个大小为4的数组归并(八八归并),如此下去。
public static void main(String[] args) {
int[] a = new int[] { 1, 2, 8, 9, 3, 4, 5, 7 };
sort(a);
}
private static void sort(int[] a) {
int LEN = a.length - 1;
aux = new int[a.length];
for (int sz = 1; sz <= LEN; sz *= 2) {// 1,2,4,8,16...
for (int lo = 0; lo < sz; lo += 2 * sz) {
//(0,2)(2,4)(4,6)...
//(0,4)(4,8)(8,12)...
//(0,8)(8,16)(16,24)...
merge(a, lo, lo + sz - 1, Math.min(LEN, lo + 2 * sz - 1));
}
}
}
归并排序是稳定排序,速度仅次于快速排序,一般用于对总体无序,但是各子项相对有序的数列。