第一章 绪论
a.计算
算法要素:
- 输入与输出、
- 基本操作、确定性与可行性
- 有穷性与正确性
- 退化与鲁棒性
- 重用性
好算法:正确(处理简单的、大规模的、一般性的、退化的、合法的输入)、健壮、可读、效率(速度快、存储空间小)
计算成本T(n):求解规模为n的问题所需基本操作数,在规模为n的所有实例中,只关注最坏(成本最高)者
b.大O记号
T(n)定义为算法所执行基本操作的总次数。
关注T(n)的渐进上界。引入大O记号。
具体地,由T(n) < = c.f(n),用f(n)代替T(n),可忽略常系数和低次项。
//常数情况
2013×2013
//包含循环情况
for(i=0; i<n; i+= n/2013 + 1);
for(i=1; i<n; i=1 << i);
//包含分支
if((n+m)*(n+m) < 4*n*m) goto UNREACHABLE;
//包含递归
if(2==(n*n)% 5) 01(n);
O(1) :常数
O(logn) 对数:此类算法非常有效,复杂度无限接近常数,可忽略常底数和常数次幂。
O(n) 线性及多项式:线性,从n到n2是编程题主要覆盖的范围。
O(2^n) 指数:无效,从多项式到指数被认为有效算法到无效算法的分水岭。指数复杂度算法无法真正应用于实际问题中,不是有效算法,甚至不能称之为算法。相应地,多项式复杂度的算法也被称作南街的(intractable)问题。
c.常见算法分析
两个任务:正确性(不变性、单调性)+ 复杂度
复杂度分析:猜测+验证,迭代(级数求和),递归(递归跟踪+递推方程)
算数级数:与末项平方同阶
幂方级数:比幂次高出一阶
几何级数(a>1):与末项同阶
收敛级数:O(1)
调和级数:1+1/2+1/3+…+1/n=O(logn)
对数级数:log1+log2+…+logn=O(nlogn)
以冒泡排序为例:
void bubblesort(int A[], int n){
for (bool sorted = false; sorted = !sorted; n--)
for (int i=1; i<n; i++)
if (A[i-1] > A[i]){
swap(A[i-1], A[i]);
sorted = false;
}
}
不变性:经k轮交换,最大的k个元素就位
单调性:经k轮交换,问题规模缩减至n-k
正确性:经至多n趟扫描后,算法必然终止且给出正确解答
d.递归及迭代
分而治之:划分为两个子问题,规模大体相当。
减而治之:划分为两个子问题,其一平凡,另一缩减。
- Fibonacci数:迭代
动态规划:按规模自小而大求解各子问题的过程。
int fibI(int n){
f = 0; g = 1;
while (0 < n--){
g = g + f;
f = g - f;
}
return g;
}
仅使用两个中间变量f和g,记录当前的一对相邻Fibonacci数。整个算法仅需线性步的迭代.
时间复杂度为O(n),空间复杂度为O(1)
- 最长公共子序列(可能有多个;可能有歧义)
对于序列A[0, n]和B[0, m],LSC有三种情况:
(1)n = -1或m = -1,取空序列(“”)
(2)A[n] = ‘X’ = B[m],取LSC(A[0, n),B[0, m)) + ‘X’ (减而治之)
(3)A[n] ≠ B[m],则在 LCS(A[0, n], B[0, m))与 LCS(A[0, n), B[0, m])中取更长者 (分而治之)
最好情况:O(n + m)
最坏情况:O(2^n),此时 (n = m)
与斐波那契数列一样,有大量重复的递归实例;若采用动态规划,只需O(nm)时间即可计算所有子问题,为此只需:
(1)将所有子问题假想列成一张表;
(2)颠倒计算方向,从LCS(A[0], B[0])出发依次计算所有项。
e.习题集
习题1-12
(unsigned long) -1 //在不知道最大无符号整数大小的情况下,只需将-1转换为此类型,轻松获得此类型的最大值
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。