迭代与递归
分而治之:分解成子问题,递归式的求解。
空间复杂度:指不包含算法的输入本身所占的空间之外,所需要的另加的用于计算所必须的空间总量。
减而治之
合 合并 |
-—例子1—–求n个总数之和
int sum(int A[], int n) { |
分析:
- 该问题分解为一个缩减问题sum(A, n-1) 和 一个平凡问题A[n-1]
- 最后规模小到一定程度时, 缩减问题变为 平凡问题
- 将两个问题合并得到结果
复杂度如何?
递归跟踪(recursion trace)
分析 (用于简单的递归)
- 检查每个递归实例
- 累积所需要时间(调用语句本身抹去,计入递归实例)
- 其总和是算法执行时间
|| ||
vv vv线性递归
:得出上述递归是线性递归,复杂度渐进O(n)
递推方程
分析 (用于复杂的递归)
上述例子中:
T(n) = T(n-1) + O(1) // recurrence
T(0) = O(1) // base
T(n) - n = T(n-1) - (n-1) = T(n-2) - (n-2) ... |
-——例子2—————–任给数组A[0,n), 将其前后颠倒
// 更一般的子区间[lo, hi]
统一接口 : void reverse(int * A, int lo, int hi);
递归版
规模缩小两个单位。
void reverse(int* A, int lo, int hi) { |
分析时间复杂度:
递归跟踪(recursion trace)
int main();
reverse(A[n], lo, hi);
reverse(A[n-2], lo+1, hi-1);
reverse(A[n-4], lo+2, hi-2);
...
reverse(A[1], lo+(n-1)/2, hi+(n-1)/2);
or
reverse(A[0], lo + n/2, hi+ n/2);
(n-1)/2 , n是奇数
/
O(1)* = O(n)
\n / 2, n是偶数
递推方程
T(n) = T(n) + O(1);
T(n) - n = T(n-1) - (n-1)
T(n) - n = T(2) - 2
= T(0) - 0
T(n) = T(0) + n = O(n)
<span style=”color”:blue”>感觉不怎么正确??
课后推敲:
迭代原始版本
void reverse_iterate_original(int* A, int lo, int hi) { |
使用next作为分支标记,goto跳转,真的能让代码运行,从来没用过
迭代精简版
void reverse_iterate(int* A, int lo, int hi) {
// 迭代版本
while (lo < hi) swap(&A[lo++], &A[hi--]);
}
分而治之(divide-and-conquer)
分解为多个或两个子问题,得到解后归并。
-—–二分递归———-
int mid_sum(int A[], int lo, int hi) { |
注意 mid + 1
// 分析:
// 被分解成两个相似问题,mid_sum(n/2)
// 规模每次缩减一半,最后到达递归基
// 将多个问题结果合并
分析复杂度:
递归跟踪(几何归纳)
以2为倍数的几何级数,总和与末项同阶。
递推方程(代数运算)
两个问题都是n/2
累加O(1)时间
递归基O(1)时间返回
递推关系
T(n) = 2* T(n/2) + O(1)
T(1) = O(1)
..
T(n) = O(n)
Max2: 迭代1
从数组区间A[lo, hi)中找出最大的两个整数A[x1]和A[x2] // A[x1] > A[x2]
比较次数要尽可能的少
int max2_three_iters(int A[], int lo, int hi) { // 1 < n = hi - lo |
总共比较n-1+n-2 =2n-3
int max2(int A[], int lo, int hi) { |
最好情况: 1 + (n-2)1 = n-1
最坏情况: 1+ (n-2) 2 = 2n-3
即使在最坏情况,也更高效的改进算法
- 分而治之
- 实现退化情况
void max2(int A[], int lo, int hi, int & x1, int & x2) { // [lo, hi) |
最坏情况: T(n) = 2 * T(n/2) + 2 <= 5n/3 -2
递推方程推导过程:
?
最好情况复杂度:
?
总结
两种重要算法策略:减而治之,分而治之
两种分析方法:递归跟踪和递推方程