第一章 算法引论
1.1 引子
1.1.1排序问题
(1)问题描述:
输入: 数字序列 X = < a 1 , a 2 , . . . , a n > X= <a1, a2, ..., an> X=<a1,a2,...,an>;
输出: 一个排列 X ′ = < a ′ 1 , a ′ − , . . . , a ′ n > X'= <a'1, a'-,..., a'n> X′=<a′1,a′−,...,a′n>。数字序列X与X’之间为满射或一一映射(即元素―—对应),并且有 a ′ 1 ≤ a ′ 2 ≤ . . . ≤ a ′ n a'1≤ a'2≤ ...≤ a'n a′1≤a′2≤...≤a′n(元素间非降序)。
例如:
√输入:8,2,2,9,3,6
√输出:2,2,3,6, 8, 9
(2)排序方法:
冒泡、插入、归并、二叉树、桶排序等。稳定的;
选择、Shell、堆、快速、组合排序等。不稳定的。
1.1.2插入排序:
(1)插入排序的原理:
通过逐步构建有序序列实现排序。对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
(1)插入排序算法的伪代码:
INSERTION-SORT (A, n)//A[1..n]
for j = 2 to n do
key = A[j]
i = j - 1
while i > 0 and A[i] > key do
A[i+1] = A[i]
i = i - 1
A[i+1] = key
(3)插入排序算法的正确性证明一基于循环不变式(LoopInvariant):
循环不变式: 在每次循环迭代之前,子数组 A [ 1.. j − 1 ] A[1..j-1] A[1..j−1]已包含了最初位于 A [ 1.. j − 1 ] A[1..j-1] A[1..j−1]、但已排好序的各个元素。
初始化: 第一轮迭代之前(即j=2),子数组A[1…j-1](即A[1])显然保持了循环不变式;
保持: 假设第j次迭代之前循环不变式为真。该算法的第j次操作只是将A[j]与已有序的A[1…j-1]中的元素进行比较,找到合适位置并插入。j+1次迭代之前,显然A[1…(j+1)-1]也保持了循环不变式;
终止: j=n+1时,显然 A [ 1.. ( n + 1 ) − 1 ] A[1..(n+1)-1] A[1..(n+1)−1](即A[1…n])已包含了最初位于A[1…n]、且已排好序的各个元素。
(4)插入排序算法的运行时间分析:
最坏情况: T ( n ) = O ( n 2 ) T(n) = O(n^2) T(n)=O(n2)。算术级数。
平均情况: T ( n ) = O ( n 2 ) T(n) = O(n^2) T(n)=O(n2)。算术级数;
最好情况: T ( n ) = O ( n ) T(n)= O(n) T(n)=O(n)。已非降序。
(5)插入排序算法的运行时间的递归方程:
{ W ( n ) = W ( n − 1 ) + n − 1 W ( 1 ) = 0 \left\{\begin{array}{l} W(n)=W(n-1)+n-1 \\ W(1)=0 \end{array}\right. {W(n)=W(n−1)+n−1W(1)=0
1.1.3归并排序:
(1)归并排序算法的原理:
基于分而治之思想,递归地把待排序序列分解为若干子序列并进行排序,再把已排序的子序列合并为整体有序序列,最终实现全序列的有序。
(2)归并排序算法的伪代码:
MERGE-SORT(A, low, high) //A[1..n]
if low < high
mid = [low + high)/ 2]
MERGE-SORT(A, low, mid)
MERGE-SORT(A, mid+1, high)
MERGE(A, low,mid, high)
MERGE(A,l, m, h) //A[1..n]
n1 = m-l+ 1//前半段长度
n2 = h-m //后半段长度
let L[1..n,+1]and R[1..n2+1] be new arrays
for i = 1 to n1 do
L[i]= A[l+i -1]//数组赋值
for j = 1 to n2 do
R[i]= A[m + j]//数组赋值
L[n + 1] = ∞
R[n + 1] = ∞
i= 1
j= 1
for k = l to h//从头到尾
if L[i]≤R[i]
A[k]= L[i]
i++
else
A[k]= R[j]
j++
(3)归并排序算法中的MERGE:
(4)归并排序算法的运行时间分析(递归树或主定理)
1.2 算法的基本概念
1.2.1算法(Algorithm):
对于计算机科学来说,算法指的是对特定问题求解步骤的一种描述,是若干条指令的有穷序列。
(1)算法的特性:
· 输入(0个或多个)
· 输出(至少1个)
· 确定性(无歧义)
· 有限性
· 可行性
(2)描述方式:
自然语言、流程图、伪代码、程序设计语言;
(3)算法与程序的区别:
程序是算法用某种程序设计语言的具体实现;
程序可以不满足算法的性质(4)。如OS。
1.2.2算法的经典问题:
排序与查找、循环赛日程表(分治法)
最长公共子序列、矩阵连乘、凸多边形最优三角剖分、加工顺序等(动态规划法)
会场安排问题、单源最短路径、哈夫曼编码、最小生成树(贪心法)
n后、最大团、图的m着色(回溯法)
0-1背包、旅行商问题、布线问题(分支限界法)
……
(1)n后问题
在n×n格的棋盘上放置彼此不受攻击的n个皇后:
据国际象棋规则,皇后可攻击与之处在同一行或同一列或同一对角线上的棋子;
n后问题等价于:如何在n×n格的棋盘上放置n个皇后,使得任何两个皇后不放在同一行或同一列或同一斜线上。
(2)0-1背包问题
W
=
{
w
1
,
w
2
,
w
3
}
P
=
{
p
1
,
p
2
,
p
3
}
max
(
x
1
p
1
+
x
2
p
2
+
x
3
p
3
)
x
1
w
1
+
x
2
w
2
+
x
3
w
3
≤
C
\begin{aligned} &W=\left\{w_{1}, w_{2}, w_{3}\right\} \\ &P=\left\{p_{1}, p_{2}, p_{3}\right\} \\ &\max \left(x_{1} p_{1}+x_{2} p_{2}+x_{3} p_{3}\right) \\ &x_{1} w_{1}+x_{2} w_{2}+x_{3} w_{3} \leq C \end{aligned}
W={w1,w2,w3}P={p1,p2,p3}max(x1p1+x2p2+x3p3)x1w1+x2w2+x3w3≤C
(3)布线问题
1.3 算法设计的一般过程
1.4 算法(复杂性)分析
1.4.1算法复杂性:
算法复杂性(亦称算法复杂度)为算法运行时所需计算机资源的度量:
时间复杂性(影响因素包括问题规模n、输入序列I、算法本身A) : T ( n , I , A ) → T ( n ) T(n,I,A)\rightarrow T(n) T(n,I,A)→T(n)
空间复杂性(影响因素包括输入输出数据IO、辅助变量V、算法本身A): S ( I O , V , A ) → S ( V ) S(IO,V,A) \rightarrow S(V) S(IO,V,A)→S(V)
很显然:
算法所需资源越多,算法的复杂性就越高;
算法所需资源越少,算法的复杂性就越低。
(1)算法(复杂性)分析:
包括:时间复杂性分析和空间复杂性分析。
方法:事后统计和事前分析。
三种情况:最好情况、平均情况和最坏情况。
(2)算法分析的意义:
算法设计:复杂性尽可能的低;
算法选用:选择复杂性最低的算法;
算法改进:算法分析有助于算法的改进。
(3)影响算法运行时间的因素(除算法本身外):
机器;
采用的语言及编译程序;
编程能力等。
(4)算法分析无需具体时间(精确或近似):
针对同一问题不同算法的比较,相对而非绝对;
应该独立于机器及实现语言;
无论科技如何发展,其运行时间的测度应该始终成立;
关心的是大的问题规模时的运行情况。→渐近复杂性
1.4.2算法的渐近(asymptotic)复杂性态:
设算法的运行时间为T(n),如果存在T*(n),使得
lim
n
→
∞
T
(
n
)
−
T
∗
(
n
)
T
(
n
)
=
C
,
C
is a constant.
\lim _{n \rightarrow \infty} \frac{T(n)-T^{*}(n)}{T(n)}=\mathrm{C}, \mathrm{C} \text { is a constant. }
n→∞limT(n)T(n)−T∗(n)=C,C is a constant.
就称T*(n)为算法的渐近性态或渐近时间复杂性。
(1)算法的渐近复杂性态示例:
假设算法A的运行时间表达式为T1(n):
T
1
(
n
)
=
30
n
4
+
20
n
3
+
40
n
2
+
46
n
+
100
T
∗
1
(
n
)
≈
n
4
(阶)
\begin{aligned} &T_{1}(n)=30 n^{4}+20 n^{3}+40 n^{2}+46 n+100 \\ &T^{*}{ }_{1}(n) \approx n^{4} \quad \text { (阶) } \end{aligned}
T1(n)=30n4+20n3+40n2+46n+100T∗1(n)≈n4 (阶)
假设算法B的运行时间表达式为T2(n):
T
2
(
n
)
=
1000
n
3
+
50
n
2
+
78
n
+
10
T
∗
2
(
n
)
≈
n
3
(阶)
\begin{aligned} &\mathrm{T}_{2}(\mathrm{n})=1000 \mathrm{n}^{3}+50 \mathrm{n}^{2}+78 \mathrm{n}+10 \\ &\mathrm{~T}^{*}{ }_{2}(\mathrm{n}) \approx \mathrm{n}^{3} \text { (阶) } \end{aligned}
T2(n)=1000n3+50n2+78n+10 T∗2(n)≈n3 (阶)
1.4.3渐近意义下的记号:
渐近上界 - O (big oh) 渐近下界 - Ω (big omega) 渐近紧确 (tight) 界 - の或 θ (theta) O (small oh) 和 ω (small omega) \begin{aligned} &\text { 渐近上界 - O (big oh) } \\ &\text { 渐近下界 - } \Omega \text { (big omega) } \\ &\text { 渐近紧确 (tight) 界 - の或 } \boldsymbol{\theta} \text { (theta) } \\ &\text { O (small oh) 和 } \boldsymbol{\omega} \text { (small omega) } \end{aligned} 渐近上界 - O (big oh) 渐近下界 - Ω (big omega) 渐近紧确 (tight) 界 - の或 θ (theta) O (small oh) 和 ω (small omega)
(1)渐近上界-O(big oh):
设f(n)和g(n)是定义在正数集上的正函数,下同。
定义:如存在正的常数c和自然数n,使得当n≥n0时有f(n)≤cg(n),则称函数f(n)当n充分大时上有界,且g(n)是它的一个上界,记为f(n)=O(g(n))。
即f(n)的阶不高于g(n)的阶。
求T(n)= 10n +4的渐近上界O:
T ( n ) = O ( n ) 或 T ( n ) = O ( n 2 ) T(n) = O(n)或T(n) = O(n^2) T(n)=O(n)或T(n)=O(n2)
① 渐近上界O运算规则:
根据О的定义,容易证明它有如下运算规则:
(1) O ( f ) + O ( g ) = O ( max ( f , g ) ) \mathrm{O}(\mathrm{f})+\mathrm{O}(\mathrm{g})=\mathrm{O}(\max (\mathrm{f}, \mathrm{g})) O(f)+O(g)=O(max(f,g));
(2) O ( f ) + O ( g ) = O ( f + g ) \mathrm{O}(\mathrm{f})+\mathrm{O}(\mathrm{g})=\mathrm{O}(\mathrm{f}+\mathrm{g}) O(f)+O(g)=O(f+g);
(3) O ( f ) O ( g ) = O ( f g ) \mathrm{O}(\mathrm{f}) \mathrm{O}(\mathrm{g})=\mathrm{O}(\mathrm{fg}) O(f)O(g)=O(fg);
(4) 如 g ( n ) = O ( f ( n ) ) g(n)=O(f(n)) g(n)=O(f(n)), 则 O ( f ) + O ( g ) = O ( f ) O(f)+O(g)=O(f) O(f)+O(g)=O(f);
(5) O ( c f ( n ) ) = O ( f ( n ) ) \mathrm{O}(\mathrm{cf}(\mathrm{n}))=\mathrm{O}(\mathrm{f}(\mathrm{n})) O(cf(n))=O(f(n)), 其中 C \mathrm{C} C 是一个正的常数;
(6) f = O ( f ) f=O(f) f=O(f) 。
② 常见的几类算法复杂性:
O ( 1 ) \mathrm{O}(1) O(1) : 常数阶;
O ( log 2 n ) , O ( n log 2 n ) \mathrm{O}\left(\log _{2} \mathrm{n}\right), \mathrm{O}\left(\mathrm{n} \log _{2} \mathrm{n}\right) O(log2n),O(nlog2n) : 对数阶;
O ( n ) , O ( n 2 ) , O ( n 3 ) , … , O ( n m ) \mathrm{O}(\mathrm{n}), \mathrm{O}\left(\mathrm{n}^{2}\right), \mathrm{O}\left(\mathrm{n}^{3}\right), \ldots, \mathrm{O}\left(\mathrm{n}^{\mathrm{m}}\right) O(n),O(n2),O(n3),…,O(nm) :多项式阶。多项式时间算法;
O ( 2 n ) , O ( n ! ) , O ( n n ) \mathrm{O}\left(2^{\mathrm{n}}\right), \mathrm{O}(\mathrm{n} !), \mathrm{O}\left(\mathrm{n}^{\mathrm{n}}\right) O(2n),O(n!),O(nn) :指数阶。指数时间算法。
③ 几类复杂性之间的关系:
O ( 1 ) < O ( log 2 n ) < O ( n ) < O ( n log 2 n ) < O ( n ) < O ( n 2 ) < O ( n 3 ) < … < O ( n m ) < O ( 2 n ) < O ( n ! ) < O ( n n ) \begin{gathered} \mathrm{O}(1)<\mathrm{O}\left(\log _{2} \mathrm{n}\right)<\mathrm{O}(\mathrm{n})<\mathrm{O}\left(\mathrm{n} \log _{2} \mathrm{n}\right)<\mathrm{O}(\mathrm{n})<\mathrm{O}\left(\mathrm{n}^{2}\right)< \\ \mathrm{O}\left(\mathrm{n}^{3}\right)<\ldots<\mathrm{O}\left(\mathrm{n}^{\mathrm{m}}\right)<\mathrm{O}\left(2^{\mathrm{n}}\right)<\mathrm{O}(\mathrm{n} !)<\mathrm{O}\left(\mathrm{n}^{\mathrm{n}}\right) \end{gathered} O(1)<O(log2n)<O(n)<O(nlog2n)<O(n)<O(n2)<O(n3)<…<O(nm)<O(2n)<O(n!)<O(nn)
(2)渐近下界-Q(big omega):
如果存在正的常数c和自然数no,使得当n > n时有f(n) ≥ cg(n),称函数f(n)当n充分大时下有界,且g(n)是其一个下界,记为f(n) =
Q(g(n))。即f(n)的阶不低于g(n)的阶。
(3)渐近紧确界–(big theta) :
如存在正的常数c1,C-和自然数n,使得当n ≥no时,有cig(n) s f(n) ≤ c2g(n),则称函数f(n)当n充分大时有紧确界,亦即g(n)和f(n)同阶。
(4)非递归算法:
① 非递归算法的时间复杂度估算:
顺序语句:各语句计算时间相加。
选择语句:条件判定语句以及各分支中语句的计算时间的较大者。
循环语句:循环体内计算时间循环次数,需考虑循环判定条件;对于嵌套循环,循环体内计算时间所有循环次数。
② 非递归算法的空间复杂度估算:
辅助变量〈不包括输入/输出变量)。
(5)递归:
① 递归的定义:
子程序(或函数)直接调用自己或通过一系列调用语句间接调用自已,称为递归。
直接或间接调用自身的算法称为递归算法。
② 采用递归算法来求解问题的一般步骤:
分析问题,寻找递归关系;
找出停止条件;
构建递归体。
③ 递归应用实例:
Fibonacci数列;
Hanoi塔;
归并排序等。
④ 递归算法的时间复杂性分析:
后向代入法:从n出发,利用递推关系计算。
猜测代入法:先猜测一个界,然后用数学归纳法证明该界是正确的。(CLRS p37,47)
递归树方法:基于递归公式。容忍少量的“草率(sloppiness)"。结点表示所花费代价,计算整棵树的代价和即得。(另附ppt)
主定理方法:给出如下递归形式的“食谱”性的方法(三种情况)(另附ppt)
⑤ 递归算法的空间复杂性分析:
辅助变量、递归栈(不包括输入/输出变量)