master公式和归并排序

剖析递归行为和递归行为时间复杂度的估算以及归并排序

剖析递归行为和递归行为时间复杂度的估算

一个递归行为的例子

master公式的使用

T(N) = a*T(N/b) + O(N^d)

  T(N)是样本量为N时的时间复杂度,N/b是划分成子问题的样本量,子问题发生了a次,后面O(N^d)是除去调用子过程之外的时间复杂度。

比如要求一个数组的最大值:

1
2
3
4
5
6
7
8
9
10
public static int getMax(int[] arr, int L, int R) {
if (L == R) {
return arr[L];
}
int mid = (L + R) >>> 1;
int maxLeft = getMax(arr, L, mid);
int maxRight = getMax(arr, mid + 1, R);
return Math.max(maxLeft, maxRight);
}
//master公式: T(N) = 2*T(N/2) + O(1);

  这里划分成的递归子过程的样本量是N/2,这个相同的样本量发生了2次,除去调用子过程之外的时间复杂度是O(1),因为求最大值和判断if复杂度是O(1),所以N^d=1,所以d=0.

那么根据如下公式判断

  1. log(b,a) > d -> 复杂度为O(N^log(b,a))
  2. log(b,a) = d -> 复杂度为O(N^d * logN)
  3. log(b,a) < d -> 复杂度为O(N^d)

  这里log(b, a)(以b为底a的对数) = log(2, 2)=1 > d=0
所以复杂度为O(N^log(2, 2))===>O(N),因此也就可以解释为什么归并排序的时间复杂度为nlogn了

归并排序

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)。

  可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
mergeSort(arr, 0, arr.length - 1);
}
public static void mergeSort(int[] arr, int l, int r) {
if (l == r) {
return;
}
int mid = l + ((r - l) >> 1);
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
public static void merge(int[] arr, int l, int m, int r) {
int[] help = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = m + 1;
while (p1 <= m && p2 <= r) {
help[i++] = arr[p1] < arr[p2] ? arr[p1++] :arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
}

总结:

  归并排序是稳定排序,他也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。