算法设计与分析 课程笔记

导引

算法是解决一确定类问题的任意一种特殊的方法

  • 数值计算方法:求解数值问题,如插值计算、数值积分等。

  • 非数值计算方法:求解非数值问题,主要进行判断比较。

算法的非形式描述:算法就是一组有穷的规则,它规定了解决某一特定类型问题的一系列运算。

五个重要特性

  • 确定性:每一种运算必须要有确切定义,无二义性。

    • 反例:5/05/0, 12+x1或2+x
  • 能行性:运算都是基本运算,原理上能用纸和笔在有限时间完成。

  • 输入:有0个或多个输入。在算法开始之前,从特定的对象集合中取值。

  • 输出:一个或多个输出。这些输出和输入有特定关系。

  • 有穷性:在执行了有穷步运算后终止。

    • 要在现代计算机上有效

    • 计算过程可以不满足有穷性

例如操作系统,当没有任务时,操作系统并不终止,而是处于等待状态,直到有新的任务启动,因而不是一个算法。

算法学习的内容

算法设计

面对具体问题,运用一些基本设计策略,规划算法。

算法表示

采用SPARKS程序设计语言。

算法确认

证明该算法对所有可能的合法输入,都能给出正确答案。

  • 穷举法

  • 数学归纳法、反证法

  • 构造性证明

  • 测试

算法分析

在最好、最坏、平均情况下分析时间、空间复杂度。

算法分析

时间的渐进表示

OO 符号

如果存在两个正常数 ccn0n_0,对于所有的 nn0n \ge n_0 ,有 f(n)cg(n)|f(n)|\le c|g(n)| ,则记作: f(n)=O(g(n))f(n)=O(g(n))

  • nn充分大时,f(n)f(n)有上界,一个常数倍的g(n)g(n)f(n)f(n)的一个上界,f(n)f(n)的数量级就是g(n)g(n)

  • f(n)f(n)的阶不高于g(n)g(n)的阶。

  • 在确定f(n)f(n)的数量级时,总是试图求出最小的g(n)g(n)

  • 有关证明中,找出ccn0n_0是关键

主要性质:

  1. O(f(n))+O(g(n))=O(max(f(n),g(n))O(f(n))+O(g(n))=O(max(f(n),g(n))

不妨设p(n)=maxf(n),g(n)p(n)= \max{f(n),g(n)}

f(n)=O(f(n))f’(n)=O(f(n)),则存在c1,n1c_1,n_1,当nn1n≥n1时, f(n)c1f(n)f’(n) ≤c_1*f(n)

设g’(n)=O(g(n)),则存在c2,n2,当n≥n2时, g’(n) ≤c2*g(n)

则O(f(n))+O(g(n)) =f’(n)+g’(n)

当n ≥max{n1,n2}时,

f’(n)+g’(n) ≤c1f(n)+ c2g(n) ≤c1p(n)+ c2p(n)=(c1+c2)*p(n)

即存在c3=c1+c2,n3= max{n1,n2},当n ≥n3时,f’(n)+g’(n) ≤c3*p(n)

O(f(n))+O(g(n)) = O(max{f(n),g(n)})

  1. O(f(n))+O(g(n))=O(f(n)+g(n))O(f(n))+O(g(n))=O(f(n)+g(n))

  2. O(f(n))O(g(n))=O(f(n)g(n))O(f(n))O(g(n))=O(f(n)g(n))

  3. g(n)=O(f(n))O(f(n))+O(g(n))=O(f(n))g(n)=O(f(n))\Longrightarrow O(f(n))+O(g(n))=O(f(n))

  4. O(cf(n))=O(f(n)),c>0O(cf(n))=O(f(n)),c>0

  5. f(n)=O(f(n))f(n)=O(f(n))

  6. A(n)=amnm++a1n+a0A(n)=a_mn^m+…+a_1n+a_0是一个mm次多项式,则A(n)=O(nm)A(n)=O(n^m)

证明:取n0=1,当n≥n0时

​ 由A(n)的定义和不等式关系|A+B| ≤ |A|+|B|有

|A(n)| = |amnm+…+a1 n+a0 | ≤ |am|nm+…+|a1 | n+|a0 |

​ = (|am|+|am-1|/n …+|a0 |/nm ) nm

​ ≤ (|am|+|am-1|…+|a0 |) nm

取 c= |am|+|am-1|…+|a0 |,有|A(n)| ≤cnm

即:A(n)=O(nm),定理得证。

Ω\Omega 符号

如果存在两个正常数ccn0n_0,对于所有的nn0n\ge n0,有f(n)cg(n)|f(n)| \ge c|g(n)|,则记作:f(n)=Ω(g(n))f(n)=\Omega(g(n))

  1. W(f(n))+ W(g(n)) = W(min(f(n), g(n)) ;

  2. W(f(n))+ W(g(n)) = W(f(n)+g(n)) ;

  3. W(f(n)) W(g(n)) = W(f(n) g(n)) ;

  4. 如果g(n) = W(f(n)) ,则W(f(n))+ W(g(n)) = W(f(n)) ;

  5. W(cf(n)) = W(f(n)) ,其中c是一个正的常数;

  6. f(n) = W(f(n))。

Θ\Theta 符号

如果存在正常数c1,c2和n0,对于所有的n≥n0,有c1|g(n)|≤ |f(n)| ≤ c2|g(n)|,则记作:f(n)= \Theta(g(n))。

若干性质

  • 传递性

lf(n)= Q(g(n)), g(n)= Q(h(n)) Þ f(n)= Q(h(n));

lf(n)= O(g(n)), g(n)= O (h(n)) Þ f(n)= O (h(n));

lf(n)= W(g(n)), g(n)= W (h(n)) Þ f(n)= W(h(n));

  • 反身性

f(n)=Θ(f(n))f(n)= \Theta(f(n))

f(n)=O(f(n))f(n)= O(f(n))

f(n)=Ω(f(n))f(n)= \Omega(f(n))

分治法

分治法适用于**nn取值相当大**,直接求解往往非常困难,甚至无法求出;将nn个输入分解成kk个不同子集,得到kk个不同的可独立求解的子问题;在求出子问题的解之后,能找到适当的方法把它们合并成整个问题的解的情况。

二分查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
procedure BINSRCH(A, n, x, j)
integer low, high, mid, j, n;
low<-1; high<-n
while low≤high do
mid← ⌊(low+high)/2⌋
//取中间值
case
:x<A(mid): high<-mid-1 //前一半
:x>A(mid): low<-mid+1 //后一半
:else: j<-mid; return; //查找成功结束
endcase
repeat
j<-0 //查找失败
end BINSRCH

算法正确性证明

  • 如果n==0,则不进入循环,j=0,算法终止。

  • 否则就会进入循环与数组A中的元素进行比较。

    • 成功情况:

      • x==A[mid],则j=mid,查找成功,算法终止。

      • 否则,若x<A(mid),则x根本不可能在mid ~ high,查找范围缩小到low ~ mid-1。反之,x>A(mid)时亦然。

    • 不成功情况:
      • 因为lowhighmid都是整形变量,按算法所述方式缩小查找范围总可以在有限步内使low>high,说明x不在A中,退出循环,j=0,算法终止。

    证明思路

    1. 检验到参数的每类取值

    2. 检验到算法的每个分支

时间复杂度分析

AAnn个元素,xxAA中元素比较

  • 成功查找:nn种情况

  • 不成功查找:n+1n+1种情况

对于一棵nn个元素的,内节点最大级数为kk的二元比较树,其k1k-1层必为满。故2k1n<2k2k-1 \le n< 2k,所以若nn在区域[2k1,2k)[2^{k-1}, 2^k)中,则对于一次成功的查找,二分查找算法至多作kk次比较,而对于一次不成功的查找,作k1k-1次比较或者作kk次比较。

设:

  • 内部路径长度II:根到所有内节点的距离之和

  • 外部路径长度EE:根到所有外节点的距离之和

则:

  • 容易证明E=I+2nE=I+2n

  • 成功查找的平均比较次数S(n)=(I/n)+1=Θ(logN)S(n)=(I/n)+1=\Theta(\log N)

  • 不成功查找的平均比较次数U(n)=E/(n+1)=Θ(logN)U(n)=E/(n+1)=\Theta(\log N)

时间复杂度最好情况最差情况平均情况
成功查找Θ(1)\Theta(1)Θ(logN)\Theta(\log N)Θ(logN)\Theta(\log N)
失败查找Θ(logN)\Theta(\log N)Θ(logN)\Theta(\log N)Θ(logN)\Theta(\log N)

空间复杂度

只用nn个位置存放数组AA,还有low,high,mid,x,jlow, high, mid, x, j五个变量需要存储,共需空间 n+5=Θ(n)n+5= \Theta(n)

归并排序

1
2
3
4
5
6
7
8
9
Procedure MERGESORT(low,high)
int low, high, mid;
if low<high
then mid= ⌊(low+high)/2⌋
call MERGESORT(low,mid)
call MERGESORT(mid+1,high)
call MERGE(low,mid,high)
endif
end MERGESORT

时间复杂度

归并排序算法消耗的时间:

T(n)={an=1,a为常数2T(n/2)+cnn>1,c为常数T(n)=O(nlogn)T(n)= \begin{cases} a&&n=1,a为常数\\ 2T(n/2)+cn&&n>1,c为常数 \end{cases} \\ \Rightarrow T(n)=O(n\log n)

任何以关键字比较为基础的排序算法,最坏情况下的时间下界都是Ω(nlogn)\Omega(n\log n),因此从数量级角度看, 归并算法是最坏情况下的最优算法。

证明

已知nn个关键字有n!n!种排列。

观察二元比较树从根到外节点的每一条路径,分别与一种唯一的排列相对应。则比较树必定至少有n!n!个外节点。且最坏情况下比较次数等于树高T(n)T(n)

则必有:$$2T(n)\ge n! \ge (\frac{n}{2})^{\frac{n}{2}} \Rightarrow T(n)=O(n\log n) $$

斯特拉森矩阵问题

矩阵An×nA_{n×n}Bn×nB_{n×n}的乘积矩阵Cn×nC_{n×n}中的元素C[i,j]C[i,j]定义为:

C[i][j]=k=1nA[i][k]B[k][j]C[i][j]=\sum_{k=1}^{n}A[i][k]B[k][j]

CCn2n^2个元素,一个元素需要做nn次乘法和n1n-1次加法,总时间复杂度为O(n3)O(n^3)

优化

如果是将矩阵直接切割成子矩阵,实际上是不能优化的,因为并没有减少子问题的实际数量。

矩阵分割

斯特拉森在分治法的基础上,利用技巧减少了子问题的个数。它用7个乘法和10个加(减)法来算出7个n2n2\frac{n}{2}*\frac{n}{2}的中间矩阵,用8个加(减)法算出子问题的解C[i][j]C[i][j],让时间复杂度从T(n)=8T(n2)+cn2T(n)=8T(\frac{n}{2})+cn^2减少到了T(n)=7T(n2)+cn2T(n)=7T(\frac{n}{2})+cn^2,时间复杂度O(nlog7)O(n^{\log 7})

二维极大点问题

在二维空间中,如果x1>x2x_1>x_2,且y1>y2y_1>y_2,那么称点(x1,y1)(x_1,y_1)支配点(x2,y2)(x_2,y_2),如果一个点没有被其他点支配,则称其为极大点。

求极大点问题就是找出一个空间中的所有极大点。

如果直接比较每一对点,时间复杂度O(n2)O(n^2)。递归优化:

  • 分:设计中位线ll,将整个点集分为两个子集SLS_LSRS_R

  • 治:分别找出SLS_LSRS_R的极大点

  • 合:基于支配规则合并SLS_LSRS_R的极大点

  • 中位线:

    • 垂直于xx轴的中位线L

    • 对集合SS中的所有点xx值排序后,寻找第n/2n/2点位置

  • 递归出口:

    • 如果集合SS中只有一个点,那么该点为极大点
  • 基于支配规则合并:

    • 对于SLS_L中的极大点pp,如果ypy_p小于SRS_R中极大点的yy值,则pp被支配,舍弃掉,因为SLS_L中的点的xx值总是小于SRS_R中的点的xx

使用排序优化后的解法时间复杂度 O(nlogn)O(n \log n)

主定理

a1,b>1a \geq 1, b > 1 为常数,f(n)f(n) 为函数,T(n)T(n) 为非负整数,且满足:

T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n)

其中 aa 表示子问题数量, f(n)f(n) 表示处理子问题消耗的时间规模。则 T(n)T(n) 有如下渐近界:

  1. f(n)f(n) 的数量级低于 O(nlogba)O(n^{\log_b a}) 则有:

    T(n)=Θ(nlogba)T(n) = \Theta(n^{\log_b a})

  2. 若:

    f(n)=Θ(nlogba)f(n) = \Theta(n^{\log_b a})

    则:

    T(n)=Θ(nlogbalogn)T(n) = \Theta(n^{\log_b a} \log n)

  3. 若存在常数 ε>0\varepsilon > 0,使得:

    f(n)=Ω(nlogba+ε)f(n) = \Omega(n^{\log_b a + \varepsilon})

    且存在常数 c<1c < 1,并且当 nn 足够大时,有:

    af(n/b)cf(n)a f(n/b) \leq c f(n)

    (或等价于 f(n)f(n) 的数量级高于 O(nlogba)O(n^{\log_b a})

    则:

    T(n)=Θ(f(n))T(n) = \Theta(f(n))


f(n)=Θ(nm)f(n)=\Theta(n^m) ,则

T(n)={Θ(nm)a<bmΘ(nmlogn)a=bmΘ(nlogba)a>bmT(n)= \begin{cases} \Theta(n^m)&&a<b^m\\ \Theta(n^m \log n)&&a=b^m\\ \Theta(n^{\log_b a})&&a>b^m \end{cases}

贪心

贪心法设计求解的核心问题是:选择能产生问题最优解的量度标准,即最优量度标准。

  • 缺点:

    • 不是对所有问题都能得到最优解

    • 基于目标函数制定的度量标准不一定是最优的

    • 最优量度标准需要经过证明

  • 优点:

    • 一旦证明成立后,它就是一种高效的算法

    • 策略的构造简单易行

    • 对许多问题都能产生整体最优解或者近似最优解

小数背包

每个物品的价值是 pip_i ,可以将每个物品的一部分 0xi10\le x_i\le1 放入背包,在满足 wixiM\sum w_ix_i\le M的情况下最大化 pixi\sum p_i x_i

只需要按价值和重量比值降序排序,选取物品直到背包装满。

作业调度

假定只能在一台机器上处理nn个作业, 每个作业均可在单位时间内完成; 又假定每个作业ii都有一个截止期限di>0d_i>0(did_i是整数),当且仅当作业ii在它的期限截止之前被完成时获得pi>0p_i>0的效益。

以目标函数pj\sum p_j作为量度标准, 将各作业按效益pip_i降序排列: p1p2pnp_1\ge p_2\ge …\ge p_n


算法设计与分析 课程笔记
https://blog.kisechan.space/2025/notes-algorithm-analysis/
作者
Kisechan
发布于
2025年3月17日
更新于
2025年3月17日
许可协议