示例与概念
- 插入排序
- 归并排序
- 最坏情况分析与平均情况分析
函数增长的渐进记号
(O(n)) 表示函数增长的上界。即,花费时间不会高于线性增长。
(Theta(n)) 同时表示上界和下界。即,花费时间一定是这个线性增长的。
(Omega(n)) 表示增长的下界。
(o(n)) 表示不渐进紧确的上界。如,(2n =O(n^2)),(n^2=O(n^2)),(2n=O(n^2)),但 (n^2neq o(n^2))
(omega(n)) 与 (o(n)) 类似,表示不紧确的下界。
此外,常用 (T(n)) 表示所需的实际时间的函数。
分析分治算法,以归并排序为例
归并排序最坏运行时间的递归式:
[ T(n)=begin{cases}Theta(1)&text{若} n=1, 2T(n/2)+Theta(n)&text{若} n>1. end{cases} ]
除使用主定理外,还可以这样理解递归式的值:将递归过程看做一个二叉树。递归调用中的每一层的总代价均为(cn) ,其中 (c) 为常数。而二叉树的层数应为 (log_2n+1) 。故,整个算法的代价期望为 (Theta(nlog_2n)) 。
分治法
分治法求最大和的子数组
分解。将数组划分为两个子数组。此时,只存在三种子数组:
- 全部位于中点左侧的子数组
- 全部位于中点右侧的子数组
- 跨越中点的子数组
解决。对于位于中点一侧的子数组,可以直接用递归解决。
对于跨越中点的子数组,将其分为左侧和右侧两部分。那么,左右两个数组都必定是所有以中点为两个边界之一的子数组的最大者。因此,从中点出发,向两侧扫描,并计算从中点到此元素的总和,找到最大。
合并。找到以上三个数组中总和最大者,即为结果。
矩阵乘法的 Strassen 算法
朴素的矩阵乘法
按照定义进行的矩阵乘法: [ C_{ij}=sum_{k=1}^{n}a_{ik}cdot b_{kj} ] 复杂度为 (Theta(n^3)) ,在此不做过多叙述。
简单的分治算法
对于 (n) 阶矩阵乘法 (C=Acdot B) ,将三个矩阵都划分为四个部分,划分的四个部分仍遵守矩阵乘法法则。即:
[ begin{bmatrix} C_{11} & C_{12} C_{21} & C_{22} end{bmatrix} = begin{bmatrix} A_{11} & A_{12} A_{21} & A_{22} end{bmatrix} cdot begin{bmatrix} B_{11} & B_{12} B_{21} & B_{22} end{bmatrix} ]
对于 (C) 矩阵中的第一部分,有:
[ C_{11}=A_{11}cdot B_{11}+A_{12}cdot B_{21} ]
其余三个等式略。
对这个算法进行代价分析。首先,由于利用下标进行原地分解,我们只需要常数时间完成分解过程。随后,对于矩阵 (C) 的每一个部分,需要进行两次递归的矩阵乘法和一次代价为 (Theta(n^2)) 的矩阵加法。共需要进行8次矩阵乘法和4次矩阵加法。最后,当“递归回升”时,(n=1) ,我们只需要进行一次标量乘法。故整体代价为:
[ T(n)=8T(2/n)+Theta(n^2) ]
需要注意:(Theta) 符号包含常数,所以常数项可以被省略,但递归不包括。所以常数系数 8 仍然保留。
应用主方法可知,(T(n)=Theta(n^3)) 。
Strassen 方法
Strassen 方法的具体流程比较复杂。算法的效果是,将每一次调用的 8 次矩阵乘法替换为 7 次矩阵乘法和常数次的矩阵加法(复杂度为 (Theta(n^2)))这也就意味着,递归树从每层 8 个分叉减少到了 7 个。随之,整个算法的复杂度降低到了 (Theta(n^{log_27})approx Theta(n^{2.81})) 。
- 将矩阵分解为四个部分。代价为 (Theta(1)) 。
- 创建 10 个 (n/2) 阶的矩阵,每个矩阵保存步骤 1 中创建的两个矩阵的和或差。代价为 (Theta(n^2)) 。
- 使用这 14 个矩阵,递归地计算 7 个矩阵积。即进行 7 次递归。
- 通过这些矩阵积计算出结果的 4 个子矩阵 (C_{11},C_{12},C_{21},C_{22}) ,代价为 (Theta(n^2)) 。
具体过程在这里省略,也可以参照这篇文章。
需要注意的是,这个算法实际上并不一定是代价更低的(二次常数因子较大)。而且,实际应用中遇到的矩阵大多是稀疏的,可以有更加实用的其他方法。因此,Strassen 方法在实际应用中并不多见。
求解递归式
代入法 / 简单的数学归纳法
递归树法
主定理
令 (ageqslant 1) 和 (b>1) 是常数,(f(n)) 是一个函数,(T(n)) 是定义在非负整数上的递归式:
[ T(n)=aT(n/b)+f(n) ]
那么:
- 若对某个常数 (epsilon) ,有 (f(n)=O(n^{log_ba-epsilon})),即 (f(n)) 的代价比前项小,那么 (T(n)=Theta(n^{log_ba})) 。
- 若 (f(n)=O(n^{log_ba})) ,那么 (T(n)=Theta(n^{log_ba}log_2n)) 。
- 若对某个常数 (epsilon) ,有 (f(n)=Omega(n^{log_ba+epsilon})),即 (f(n)) 的代价比前项大,那么 大专栏 算法导论笔记(一):复杂度,分治,随机">(T(n)=Theta(f(n))) 。
简要地说,可以这样理解主定理:由递归带来的代价为 (Theta(n^{log_ba})) 。如果式中的常量更大,则常量决定了代价增长的速度。反之,就是递归决定了函数增长的速度。若二者相等,则需要乘上一个系数 (log_2n) 。从递归树解法可以证明主定理。
概率分析和随机算法
平均情况运行时间
随机算法:算法中使用随机数生成器,输出不仅取决于输入
指示器变量:对于事件 (A),指示器随机变量 $ (A)=
[begin{cases}1&text{如果}Atext{发生},\ 0&text{如果}Atext{不发生}.end{cases}]$ 。 如,抛硬币的随机指示器期望为 (1/2) 。
雇用问题
问题描述
你需要一名办公助理。为此,你雇佣了一名 HR ,每天为你带来一位面试者。这个过程需要为 HR 支付一笔面试费(算法中的代价)。每当你遇见一位比现在的办公助理更好的助理,就换掉现在身边的。这个过程需要为 HR 支付更多的费用(切换时需要更高的代价)。估算这个过程的费用是多少。
该问题可以简化为:遍历一个序列,每当遇到比当前内存中的变量更高的值,就换成序列里的值。在两个变量之间做比较的代价较小,切换的代价较大。
显然,在最坏情况下,每一次面试都需要进行切换。这时,代价的期望为 (O(c_hn)) 。其中, (c_h) 是雇佣一个新的助理的代价。
使用随机指示器分析雇用问题
显然,决定面试者是否被雇佣的随机指示器的期望 (mathrm{E}[X_i]=1/i) 。这意味着每个面试者有 (1/i) 的概率比之前的所有人都好,因此被雇佣。那么总的雇佣人数的期望为: [ mathrm{E}[X]=mathrm{E}sum_{i=1}^nX_i=sum_{i=1}^nfrac 1i=ln n+O(1) ] 因此,在面试者随机出现的情况下,代价的期望为 (O(c_hln n)) 。为了逼近这个值,可以在算法开始之前先进行随机化。
随机算法
随机排列数组
permut-by-sorting. 具体做法是:对于输入序列,生成一个相同长度的随机数的数组。为了让数组的内容足够唯一,让随机数取值在 ((1, n^3)) 范围内。然后,在排序这个数组的同时,排序序列。这个做法的代价与比较排序的代价相等,为 (Theta(nlog_2n)) 。
原地随机化
从1到n,将当前元素与序列后方的任意一个元素交换。
其他随机相关问题
球与箱子问题
向 (b) 个箱子中投球,落入每个箱子的概率均为 (1/b) 。这个问题对于散列算法十分有用。我们称落入一个新的箱子中为一次命中。我们以已经有球的箱子的数量划分阶段。如,第1阶段表示尚未投球,第2阶段表示已投入一个。那么,对于已经有 (i-1) 个命中的情况,即第 (i) 阶段,得到一次命中的概率为 ((b-i+1)/b) 。该阶段需要投球次数的期望为: [ mathrm{E}[n_i]=frac b{b-i+1} ] 整个过程共需要投球次数的期望为: [ mathrm{E}[n]=mathrm{E}[sum_{i=1}^bfrac b{b-i+1}]=mathrm{E}[sum_{i=1}^bfrac 1i]=b(ln b+O(1)) ] 所以,我们大约要投 (bln b) 次才能保证所有箱子里都有球。这个问题也被称为礼券收集者问题。
特征序列
抛一枚硬币 (n) 次,最长的连续正面的序列有多长?这个值的期望为 (Theta(log_2n)) 。可以从上界和下界两个方向来证明。
- 布尔不等式:一组事件并集的概率不大于这些事件概率的和,无论这些事件是否独立。
在线雇用问题
雇用问题的一个变形:面试一批面试者,并最终雇用其中的一个。但是,对于每一个面试者,需要立刻决定是否雇用。在这个问题中,我们需要在面试者的质量和面试次数之间取得平衡。
一个有用的策略是:先淘汰 (k) 名面试者,找到他们当中最好的一个。随后,在接下来的面试中,一旦遇到比前 (k) 个都好的,就立刻雇用。
我们首先来尝试计算得到最好面试者的概率。设最好面试者在第 (i) 个且被我们取到的事件为 (S_i) ,考虑到前 (k) 个面试者直接被我们排除掉,其中,当 (ileqslant k) 时,不可能取到最佳面试者。于是,取得最好面试者的概率密度函数为: [ Pr{S}=sum_{i=1}^nPr{S_i}=sum_{i=k+1}^nPr{S} ] 接下来求 (Pr{S_i}) 。当 (S_i) 发生时,第一,应聘者应该在第 (i) 个位置。第二, (k) 到 (i-1) 范围内的所有值全部小于前 (k) 个中的最大者。这两个事件是独立的。对于第二个事件,将其转化为:从 (1) 到 (i-1) 范围内的最大值出现在前 (k) 个中。这个概率为 (k/(i-1)) ,故 [ Pr{S}=sum_{i=k+1}^nfrac k{n(i-1)}=frac knsum_{i=k}^{n-1}frac 1i ] 又,利用积分来近似约束这个和数,有: [ int_k^nfrac 1xmathrm dx leqslant sum_{i=k}^{n-1}frac 1ileqslantint_{k-1}^{i-1}frac 1xmathrm dx ] 求解可以得到下面的上下界: [ frac kn(ln n-ln k) leqslant Pr{S}leqslantfrac kn(ln(n-1)-ln(k-1)) ] 以 (k) 为未知数求导。当 (k=frac ne) 时,下界取得最大值。
**因此,以此决定我们的 (k) 取值,那么将以至少 (frac 1e) 的概率雇用到最好的面试者。