1. 简介
分治递归(Divide and Conquer)是一种计算机科学中常用的算法设计思想,是将原问题划分为若干个子问题,递归地求解子问题,再将子问题的解合并得到原问题的解的一种方法。这种方法常用于排序、搜索、最优化和计算几何等领域。
高级主定理(Master Theorem)是后来人们对该算法的运行时间分析公式进行总结提炼而得到的。该定理的基本形式适用于许多使用分治算法设计的算法,尽管它不能应用于所有种类的分治递归。本文将详细介绍高级主定理及其运用。
2. 基本概念介绍
2.1 递归式的形式
递归算法通常可以表示为递归式的形式,它描述了原问题如何被分解成若干个子问题以及子问题如何被合并为原问题的解。通常,递归式具有以下形式:
其中,T(n) 表示 n 个元素的问题的规模,a 表示需要子问题的数量,n/b 表示每个子问题的规模, f(n) 表示合并子问题的解所需的时间复杂度。
2.2 渐进符号
在分析算法时,人们通常使用渐进符号来描述算法的时间复杂度。渐近符号用来描述函数的增长或下降速度。以下是常见的渐进符号:
符号 | 名称 | 含义 |
---|---|---|
O | 大O符号 | 上界,表示函数在增长趋势上不超过另一个函数 |
Ω | 大Ω符号 | 下界,表示函数在增长趋势下不低于另一个函数 |
θ | Theta符号 | 渐进上下界,表示函数在增长趋势上和下都受到另一个函数的限制 |
3. 高级主定理
高级主定理是一种描述递归算法时间复杂度的公式。它的形式如下:
其中 a 是子问题的数量,n/b 是每个子问题的规模,f(n) 是合并子问题的解所需的时间复杂度。
如果存在常数 c 和 k,使得对于足够大的 n,递归式可以表达为以下形式:
如果 f(n) = O(n^(logba-c)),则 T(n) = Θ(n^(logba))
如果 f(n) = Θ(n^(logba-c) logkn),则 T(n) = Θ(n^(logba) logk+1n)
如果 f(n) = Ω(n^(logba-c)),且存在常数 d 使得对于足够大的 n,有 af(n/b) ≤ df(n),则 T(n) = Θ(f(n))
直观上来说,这个公式告诉我们,如果我们可以将算法递归分解为 a 个大小为 n/b 的子问题,并且可以用复杂度为 f(n) 的函数来整合它们的解决方案,则递归算法的总运行时间为 T(n)。这个公式给出了三个可以解决的条件,对于每个条件,它告诉我们递归算法的总运行时间,以多少次 n 的函数增长,准确地反映了算法的时间复杂度。
4. 经典例子
4.1 归并排序
我们以归并排序为例,演示如何使用高级主定理来分析分治递归算法。归并排序是一种经典的排序算法,它使用分治策略:
Divide: 将 n 个元素的序列分成两个长度为 n/2 的子序列
Conquer: 让递归调用归并排序算法对两个子序列进行排序操作
Combine: 合并排序后的子序列以产生已排序的答案
归并排序的递归式为:
其中,子问题的数量为 a = 2,每个子问题的大小为 n/b = n/2,子问题的整合需要时间复杂度为 f(n) = Θ(n),因为合并两个子序列的复杂度是 Θ(n)。而我们可以证明 f(n) 是渐进上界 O(n(log(n))),根据条件一,T(n) 就是 Θ(n log(n)) 的。
void merge(int a[], int lo, int mid, int hi) {
int i = lo, j = mid+1;
int k = 0;
int tmp[hi-lo+1];
while(i <= mid && j <= hi) {
if(a[i] <= a[j]) tmp[k++] = a[i++];
else tmp[k++] = a[j++];
}
while(i <= mid) tmp[k++] = a[i++];
while(j <= hi) tmp[k++] = a[j++];
for(int p = 0; p < k; ++p) a[lo+p] = tmp[p];
}
void merge_sort(int a[], int lo, int hi) {
if(lo >= hi) return;
int mid = lo + (hi - lo) / 2;
merge_sort(a, lo, mid);
merge_sort(a, mid+1, hi);
merge(a, lo, mid, hi);
}
4.2 快速排序
快速排序是另一个着名的分而治之排序算法。与归并排序不同,快速排序遵循两个步骤:
Partition:从数组中选择一个元素称为枢轴,然后将数组分成两部分,使得小于枢轴的元素位于枢轴的左侧,大于或等于枢轴的元素位于其右侧
Recursion:递归地对子集排序。
快速排序的递归式为:
其中,子问题的数量为 a = 2,每个子问题的大小为 n/b = n/2,子问题的整合需要时间复杂度为 f(n) = Θ(n),因为快速排序的平均时间复杂度为 Θ(n
log n)。根据条件一,T(n) 就是 Θ(n log(n)) 的。
int partition(int a[], int lo, int hi) {
int pivot = a[hi];
int i = lo - 1;
for(int j = lo; j < hi; j++) {
if(a[j] <= pivot) {
i++;
std::swap(a[i], a[j]);
}
}
std::swap(a[i+1], a[hi]);
return i + 1;
}
void quick_sort(int a[], int lo, int hi) {
if(lo < hi) {
int p = partition(a, lo, hi);
quick_sort(a, lo, p - 1);
quick_sort(a, p + 1, hi);
}
}
5. 总结
高级主定理是一种简明而有力的工具,用于分析大量复杂递归算法的时间复杂度。但需要注意的是,高级主定理并不适用于所有递归算法,因为它适用于递归解决方案可以精确地表示为一个类似于归纳关系的有限次递归调用的情况。如果一个递归算法不能很好地满足这个要求,那么它往往需要经过深入的分析来确定它的时间复杂度。