cs_notes

MIT 6.046J Design and Analysis of Algorithms, Spring 2015

https://www.youtube.com/playlist?list=PLUl4u3cNGP6317WaSNfmCvGym2ucw3oGp

1. 1. Course Overview, Interval Scheduling

1.1 课程概览

本课程内容包括:

本课程将在006基础上提高难度,探讨算法在理论和实际应用上的差异。

1.2 可解性与难度

Hamilton环问题是NP完全问题的一个例子。

1.3 区间调度问题

区间调度问题指在有限资源下安排多个任务的问题。

具体来说,问题设置如下:

问题目标是找到一组不重叠的任务进行安排,利用资源最大限度地完成任务。

随着问题设置的增加,问题的难度也在变化:

2. 2. Divide & Conquer: Convex Hull, Median Finding

凸轮图

凸轮图是指包含所有点的最小凸多边形。给定平面上的一组点(x,y坐标),求解凸轮图就是找出形成凸轮图轮廓的所有线段。

线段表示为连接两个点形成的线。只有当线将平面分割成一个半平面内含所有点时,该线才是凸轮图的一部分。

直接穷举算法中,对每个点对生成线段,检查是否满足上一条件。时间复杂度为O(n^3)。

使用分治法可以提升效率:

  1. 将点集根据水平轴分为左右两个子问题集
  2. 对每个子问题集递归求解凸轮图
  3. 合并两个子问题凸轮图,得到原问题凸轮图

合并阶段就是找到两个子图公共的线段,再添加横跨两子图的新线段。时间复杂度降低至O(nlogn)

中值问题

中值问题是找出一个数据集合的中位数。

直接方法为先排序,然后输出中间元素。时间复杂度O(nlogn)

使用分治法:

  1. 将数据随机分割成两组
  2. 对每个子问题求解中值
  3. 合并两组中值

两组中值就可以限定原问题中值的可能范围。重复上述过程可以在O(n)时间内求解。

3. R1. 矩阵乘法和主定理

1. 矩阵乘法的定义

矩阵乘法是将两个矩阵的元素相乘并求和得到新的矩阵。

具体来说,如果A是m行n列的矩阵,B是n行p列的矩阵,则A与B的乘积C是m行p列矩阵,其元素c_{i,j}计算为:

c_{i,j}=a_{i,1}b_{1,j}+a_{i,2}b_{2,j}+…+a_{i,n}*b_{n,j}

2. 矩阵乘法的时间复杂度

对于m行n列矩阵A和n行p列矩阵B,其时间复杂度为O(mnp),即需要mnp次的乘加操作。

3. 主定理

主定理给出了递归算法时间复杂度的一般公式。

设递归问题可以分解成a个大小相同的子问题,每个子问题的规模是原问题的1/b分之一。解决每个子问题需要O(n^d)的时间。

则该递归算法的时间复杂度为:

其中a、b、d为问题固有的常数。

4. 矩阵乘法应用主定理

矩阵乘法将原问题分解成n/2个大小相同的子问题,每个子问题规模为原问题的1/2。解决每个子问题需要O(n^3)的时间。

这里a=b=2,d=3,满足d=log_b a条件。

根据主定理,矩阵乘法的时间复杂度为O(n^3logn)。

4. 3. Divide & Conquer: FFT

多项式可以用以下几种表示方法:

A. 系数向量表示法

用向量a=(a0,a1,…,an-1)表示多项式各项的系数。

B. 样本点表示法

用(x1,y1),(x2,y2),…,(xn-1,yn-1)表示多项式在各个点的取值。其中xi为采样点,yi为对应点的值。只需要n-1个样本点就能完全确定一个n次多项式。

多项式运算常见操作包括:

  1. 评估:给出多项式a(x)和一个点x0,计算a(x0)的值。利用Horner算法可以在O(n)时间内完成。

  2. 加法:给出两次多项式a(x)和b(x),计算它们和c(x),其中c(x)=a(x)+b(x)。直接相加对应的项即可,也是O(n)时间。

  3. 乘法:给出两次多项式a(x)和b(x),计算它们积c(x),其中c(x)=a(x)×b(x)。直接乘对应项会导致时间复杂度为O(n^2)。利用分治思想,可以设计出一个O(nlogn)的FFT算法来完成。

FFT算法的思想来源于多项式乘法与卷积运算之间的等价关系。卷积运算常用在数信号处理如MP3压缩等场景,也就是给出两个信号向量,计算它们在所有偏移量下的内积和。这等价于给出两次多项式,计算它们的积。FFT可以高效地计算卷积,从而解决了乘法问题。

5. R2. 2-3 Trees和B树

2-3树和B树都是平衡搜索树,用于实现查找效率为O(logn)的字典型数据结构。

2-3树

2-3树是一种近似平衡的多叉搜索树。每个节点至少有2个子节点,最多有3个子节点。

搜索过程与二叉搜索树类似,根据键值与节点中的键值进行比较,选择对应的子树继续搜索。

B树

B树扩展了2-3树的概念,具有更高的节点容量和更宽松的平衡标准:

搜索过程与2-3树相同,按键值与节点内键值比较后选择对应的子树搜索。

B树相对二叉搜索树,能更好利用内存高速缓存,降低磁盘访问次数。因为B树节点容量大,每次从磁盘读取整个节点。

最后,2-3树和B树的搜索、插入和删除复杂度均为O(logn)。

6. 4. Divide & Conquer: van Emde Boas Trees

分离数据结构成为簇

万鬼·巴斯树结构将数据元素分离到宇宙里不同的簇(称为星系)。每个簇内部使用一个简单的数组来存储元素。数组的每个单元对应数据元素的值,存储0或1来表示该元素是否包含在集合中。

建树表示簇的并集

为每个簇建立一颗树,树的每个节点表示两个下级子节点对应的簇的并集。根节点表示所有簇的并集。通过计算节点的0/1值,可以快速排除不含元素的簇,实现 succeeds 查询的加速。

将宇宙划分成更小的簇

可以继续将每一个簇(星系)再细分为更小的簇,对新的簇重复上述构建树的过程。通过多次递归,实现问题规模从u逐步缩小为$\sqrt[2]{u}$的效果,从而将时间复杂度降低到O(loglogu)。

实现 predecessor 和 successor 查询

利用树结构进行二分搜索,先在顶层树中判断元素属于哪个更大簇,然后在该簇对应的子树中继续搜索。以O(loglogu)的时间复杂度支持前驱 predecessors 和后继 successors 查询。

万鬼·巴斯树在路由器中的应用

万鬼·巴斯树适用于IP地址范围查询的路由表应用场景。IP范围划分为簇,利用树表示不同范围的“或”关系,快速定位包的发送端口。与平衡二叉搜索树O(logn)相比,万鬼·巴斯树大大降低了时间复杂度。

7. 5. Amortization: Amortized Analysis

平均分析法概述

平均分析法是一种分析数据结构操作成本的方法。它允许某些操作的实际时间成本高于平均时间,只要所有操作的总时间负担在一个合理的界限内。

累计法

累计法是最简单的平均分析方法。它计算所有操作的总时间成本,然后将其除以操作数,得到每个操作的平均时间成本作为界定。

例一:数组扩容

在哈希表中使用链地址法时,数组大小m如果小于元素数量n,需要扩容数组。扩容操作包括分配新数组、重新散列以及复制元素,时间成本为O(m)。但是如果每次扩容都翻倍m,总共只需要O(logn)次扩容,合计时间成本为O(n)。因此每个插入操作的平均时间成本是常数级。

例二:2-3树的插入和删除

假设2-3树上的操作包括:创建空树(常数时间)、插入(O(logn)时间)和删除(O(logn)时间)。我们可以认为删除的平均时间成本是0,因为删除元素数量不能超过插入元素数量。将三种操作的实际总时间成本加起来,最多为插入数量×O(logn),所以平均每个操作的时间成本是O(logn)级。

会计法

会计法将时间视为货币。操作可以向“银行账户”存入“钱”,以补偿将来可能出现的高成本。插入操作可以向账户存入一定数量的“钱”,以支付将来因为结构调整可能产生的额外成本。删除操作从账户中提取之前存储的“钱”来抵消实际成本。只要账户余额永远大于等于零,就可以确保平均每个操作的时间成本在一个合理的界限内。

总结

平均分析法通过将操作划分为各种类型,并给每个类型操作指定一个时间界限作为其平均成本,从而管理结构中的成本,确保结构的整体时间效率。它允许个别操作的实际成本暂时超出平均成本,只要在长期内都在预期范畴内即可。这对许多算法和数据结构的设计和分析都很重要。

8. 6. Randomization: Matrix Multiply, Quicksort

随机算法定义

随机算法是会产生随机数的算法。随机数可以是海变率、实数或向量。算法会根据随机数的值作出决定。

随机算法可以是递归的。在每级递归中都会产生随机数。因此,不同执行可能得到不同结果或花费不同时间。

分类

随机算法按正确性和效率可分为:

例子

矩阵乘法简单算法

矩阵乘法的简单算法时间复杂度是O(n^3)。它通过计算各行各列的乘积来得到积矩阵中的每个项。

在算法分析中,我们只考虑乘法次数,因为乘法在计算机中比加法更耗时。

更好的矩阵乘法算法

9. R4. 随机选择与随机快速排序

随机选择算法

随机选择算法是采用随机化思想的中位数查找算法的一种广泛化版本。其工作原理如下:

  1. 从数组中随机选择一个元素X作为划分点
  2. 将比X小的元素放在它的左边,比X大的元素放在它的右边
  3. 如果X等于给定的目标元素K,则返回X
  4. 否则,如果X小于K,就在X左边的子数组中递归查找;如果X大于K,就在X右边的子数组中递归查找
  5. 每次划分都减小问题规模的一半左右

由于选择划分点X是随机的,无法确定 problem size 的减小程度。因此我们分析其平均运行时间:

设 T(n) 为问题大小为 n 时的平均运行时间。可以写出递归关系:

T(n) = O(n) + Max{E[T(K-1)], E[T(n-K)]}

其中 K 是随机选择的划分点。将其展开为期望,得到T(n) = O(n)

随机快速排序算法

随机快速排序算法与随机选择算法差异仅在于:

T(n) = O(n) + E[T(K-1)] + E[T(n-K)]

展开后也可以得到 T(n) = O(nlogn) 的平均运行时间。

总之,通过随机化选择划分点,随机选择算法和随机快速排序算法都获得了线性或对数线性的平均运行时间,比确定性算法有明显优势。

10. 7. Randomization: 随机化技术:跳跃表

跳跃表是一种随机化的数据结构,它能在平均情况下实现对数级时间复杂度的搜索、插入和删除操作。

跳跃表的基本思想是:构建多个有序链表,高层链表包含了低层链表的部分节点。高层链表实现长距离跳跃,低层链表实现精细搜索。

构建跳跃表时,每个节点都有概率生成在高层链表中。生成方法是:给每个节点一个“层数”,层数符合几何分布。最底层为0层,向上层数增加。

这样一来,越往上层链表的节点越少,但范围越大,实现跳跃效果。最顶层可能只有个别节点,但可以使搜索跳过很大范围。

跳跃表的基本操作

跳跃表的搜索算法如下:

  1. 从最高层链表最左端开始向右搜索。

  2. 找到第一个大于或等于目标值的节点位置,判断是否到达链表尽头。

  3. 如果没有超过,说明需要下跳,进入下一低层链表继续向右搜索。

  4. 如果超过了,说明需要返回,进入同层链表向左搜索。

  5. 按此方法,我们依次搜索各层链表,直到找到目标值或搜索失败为止。

插入和删除的具体实现方式也比较简单:

跳跃表的时间复杂度分析

由于跳跃表采用随机方式构建,其基本操作时间复杂度是一个随机变量。

通过概率分析可以得到,当 n 个元素时,搜索的平均时间复杂度为O(logn)。

而且,我们可以通过进一步分析证明:对任意给定的常数c,当n足够大时,搜索时间小于c*logn的概率大于1-1/n^c。

这就是我们所说的“以很高概率”时间复杂度为O(logn)。这比平均时间强很多,提供了更强的保障。

总之,通过简单但有效的随机化技术,跳跃表实现了使用链表就能获得的最优对数时间搜索效率。而且证明也相对简单。它是一种非常优秀的动态数据结构。

11. 8. Randomization: Universal & Perfect Hashing

1. 字典问题规格

字典问题规定了如下三个操作:

  1. 插入一个 item
  2. 删除一个 item
  3. 查找一个 item

查找操作只是简单地判断该 key 是否存在,如果不存在则无法获取任何信息。

2. 哈希表基本原理

哈希表包含两个重要变量:

哈希函数会将 key 映射到 0 至 m-1 的槽位上,每个槽位形成一个链表存储哈希到该槽位的所有 item。

通过分析得到哈希表的平均搜索时间复杂度为 O(1 + α),其中 α 为负载因子 n/m。

3. 简单统一哈希假设

6006 课程分析时假设哈希函数满足“简单统一哈希”,即:

但这是一个不切实际的平均案例假设。

4. 完美哈希与通用哈希

4.1 完美哈希

保证不同 key 永远不会映射到同一槽位,此时复杂度为 O(1),但只适用于静态数据。

4.2 通用哈希

通过随机选择哈希函数来引入随机性。我们从一组哈希函数家族中随机选择一个函数作为映射函数。

此方法可以实现动态数据的 O(1) 之内时间复杂度,且不需要任何 average case 假设。

5. 通用哈希算法分析

选择哈希函数家族使不同 key 映射到同一槽位的概率为1/m。

通过随机选择哈希函数,期望解决冲突的槽位链表长度为 O(1),从而实现期望时间复杂度为 O(1) 的插入、删除和查找操作。

12. R5. 动态规划

温习动态规划

动态规划的主要思想是将问题分解成子问题,并利用已经解决的子问题的结果。在课程中我们经常关注算法的时间复杂度。

例子一:机器人路径计数

给定一个M×N格子,一个机器人从(1,1)出发,每次只能向右或向上移动一格,求从起点到终点(M,N)有多少条不同的路径。

分解问题:每个点的路径数等于其左上角和上方点路径数之和。所以时间复杂度为O(MN)。

例子二:制造零钱问题

给定面额硬币{1,5,10,…}和一个金额N,找出组成N的最少硬币个数。

暴力解:尝试所有组合。若选择面额为s的硬币,则子问题为原问题减去s。时间复杂度O(MN)。

这个问题实际上等价于背包问题,但使用暴力解法得到的时间复杂度没有正常体现问题的真实复杂性。

例子三:矩形块堆叠问题

给定N种不同大小的立方体块,每个块有长度、宽度、高度。能够将某块B放在块A上方的条件是B的长度和宽度均小于A。要求堆叠块的最大高度。

分解问题:对每个块,找到其兼容的剩余块集合,与之组成子问题。兼容块集合大小不超过N。时间复杂度O(N^2)。

分析动态规划算法的时间复杂度

  1. 计算子问题个数的上限
  2. 估算在子问题与原问题之间处理信息的时间复杂度
  3. 根据1和2导出总时间复杂度

13. 9. Augmentation: Range Trees

简介

数据结构增强技术可以将其他数据结构进行改造,增加额外功能。

边界树

边界树可以支持在有序数集合中快速查询哪些元素处于给定范围内。

数据结构

边界树是一种平衡二叉搜索树,每个节点额外存储以该节点为根的子树中最小和最大元素的值。

操作

实现

实现边界树需要满足数据结构增强的条件:

这样可以保证插入、删除和范围查询的时间复杂度都是O(logN + K)。

14. 10. Dynamic Programming: Advanced DP

动态规划基本原理

动态规划是一种算法技术,可以用来解决时间复杂度看似指数级的问题。它的核心思想是:

  1. 将原问题分解成一系列子问题
  2. 表达子问题与原问题之间的优化子结构性质
  3. 动态规划 table 记录子问题的解,避免重复计算

最长回文子序列问题

给一个字符串序列,找出其最长的回文子序列。

假设序列为 x1,…,xn,那么:

  1. 单个字母也视为回文序列,长度为1
  2. 子序列必须保留原序列中的顺序
  3. 回文序列读反还是原来的序列

用 lij 表示子序列 xi,…,xj 中最长回文子序列的长度。

DP 算法

  1. 基础情况:若 i == j, li,j = 1
  2. 若 xi == xj,则 li,j = 2 + li+1,j-1
  3. 若 xi != xj,则 li,j = max(li,j-1, li+1,j)

最长回文子序列问题的解

  1. 定义状态:lij 表示子序列 xi 到 xj 最长回文子序列长度
  2. 表达子问题与状态转移:
    • 基础情况为单字母长度1
    • 两端字母相同时加入这两个字母
  3. 使用 DP table 存储每个子问题的解
  4. 具体实现可以采用迭代或递归+备忘录方式

更难的动态规划问题

随着问题难度增加,状态定义和状态转移关系会更为复杂。但动态规划的基本思想:分解问题->表达子结构->避免重复计算仍然成立。

15. 11. Dynamic Programming: 所有点对最短路径

最短路径问题定义

在有向图中,给定源点s到其他任意点v的最短路径权值定义为δ(s,v)。权值可能为正 infinity 或者负infinity:

如此定义可以使得最短路径问题在任何情况下都是良好定义的。

单源最短路径算法复习

所有点对最短路径问题定义

给定有向图G(V,E)和权值函数w,找出对任意点对u,v都计算δ(u,v)。

所有点对最短路径算法

1. 单点算法V次

分别从每个点运行单源算法,时间复杂度为:

2. Johnson算法

对一般权图,可以通过Johnson算法在O(V^2logV+VE)时间内解决问题,优于直接运行V次单源算法。这证明了即使存在负权也可以使用与非负权相同的效率来解决问题。

16. 12. Greedy 算法:最小生成树

贪心算法是解决一个问题时,总是做出在本地最佳的选择的算法。它常用于一些子问题具有最优子结构的问题。

最小生成树问题

最小生成树问题是找出一棵包含所有顶点的树形结构,且该树的边权和最小。

给出一个带权有向图G。权值函数W给出每条边的实数权值。目标是找出G的一个生成树,且其总边权和最小。

生成树即包含G所有顶点的连通无环图。

贪心算法理论

贪心算法一般需要满足以下两个性质:

  1. 最优子结构性(Optimal Substructure):如果子问题能够得到最优解,那么原问题就能得到最优解。

  2. 贪心选择性质(Greedy Choice Property):通过执行一系列本地最优选择,就能得到全局最优解。

最小生成树的最优子结构性

假设知道最小生成树中的一条边e,则可以用以下方法简化问题:

  1. 删除边e后,图分成两个部件A和B

  2. 独立求出A和B中的最小生成树

  3. 将A和B的最小生成树通过边e连接起来,即为原问题的最小生成树

这说明如果子问题(A和B部分)能得到最优解,原问题也能得到最优解。

最小生成树算法

  1. prim算法:从任意一个点开始,每次选择与已构造树相连并且权值最小的边,加入树中,直到构成一棵生成树。

  2. Kruskal算法:按边权从小到大排序所有边,然后依次添加不形成回路的边,直到成为一棵生成树。

两种算法的时间复杂度均为O(ElogE),其中E为图中的边数。它们都满足贪心选择性质。

17. R6. 贪心算法

不断采矿问题

假设有n种金属,每种金属的价值以美元/千克表示。需要获得价值T美元的礼物。每种金属的数量有限制。问题是用最少的总重量获得价值T美元。

贪心算法:

  1. 按金属的单位价值从高到低排序

  2. 取价格最高的金属,计算采取的数量

  3. 如果数量大于限定值,则全部采取;如果小于限定值,则采取足够的数量

  4. 重复步骤2,直至价值累加达到T

证明:

如果不采用单位价值最高的金属,而采用单位价值次高的金属,则采集的总重量将更大。

进程调度问题

给定n个进程,每个进程时间Ti(i=1~n)。问题是如何安排顺序,使平均完成时间最小。

平均完成时间定义为:所有完成时间之和/进程数

每个进程的完成时间定义为:前面所有进程耗时之和

贪心算法:

  1. 按进程时间从小到大排序

  2. 安排顺序依次为排序后的进程顺序

证明:

交换任意两次顺序,都可以减小平均完成时间。

事件安排问题

给定一组事件区间。每个事件有开始时间和结束时间。问题是用最少的”人”或者”机器”完成所有事件。

贪心算法:

  1. 按结束时间从早到晚排序事件

  2. 依次安排第一个未安排事件到当前”人”或”机器”上

  3. 如果时间重合,则新建”人”或”机器”

  4. 重复2-3步,直至所有事件安排完毕

证明:

所得到解是最优解,因为贪心选择顺序不会使解变差。

18. 13. Incremental Improvement: Max Flow, Min Cut

流网络

流网络是一个有定向边的图,其中包含源点s和汇点t。每个有定向边(u,v)都有非负容量C(u,v)。如果(u,v)不在图中,则C(u,v)=0。

流是一个将源点s到汇点t的数字分配函数。对每个有定向边(u,v),流f(u,v)必须小于等于边的容量C(u,v)。对除s和t之外的任意点v,输入流与输出流之和必须相等,遵守流的守恒定律。

最大流问题

最大流问题是找到一种流函数,使源点s流出的数量最大,同时满足所有边和点约束。这是一个线性规划问题。

剪点和割面

割面是将源点与汇点分离开来的一组边的集合。剪点是将图分成源点子图和汇点子图的一组点的集合。

最大流最小割定理

最大流最小割定理指出在任何流网络中,最大流的量等于最小割的容量。它是流网络的一个重要定理。

福特-福尔克森算法

最大流最小割定理导致第一个最大流算法 - 福特-福尔克森算法。它通过不断增量地从残余网络中增加流来求解最大流问题,直到无法再增加流时停止。

19. 14. Incremental Improvement: Matching

网络流是一个有向图,每个边都有两个数字相关联,一个流值和一个容量。

流网络满足以下几个规则:

  1. 流的大小不能超过边的容量。

  2. 除源点和汇点外,每个节点流入的流量等于流出的流量(流守恒)。

最大流问题是找到流网络中从源点到汇点的最大可能流量。

Ford-Fulkerson算法解决这个问题,它通过不断在残余网络中搜索增广路来增加流值,直到没有增广路为止。

残余网络Gf是一个依赖当前流f的新网络,它包含那些容量大于流值的边。

增广路是一个源点到汇点的路径在残余网络Gf中。如果存在增广路,流还未达最大,可以沿增广路增加流值。

增广路的残余容量定义为增广路上各边最小的残余容量。每次沿增广路增加的流值就是该残余容量。

Ford-Fulkerson算法初始化将所有边流值设为0,循环搜索Gf中的增广路,并使用增广路的残余容量来增加流值,直到找不到增广路为止,此时流就达到最大。

最大流最小割定理表明:最大流等于任意一个割(通过源点和汇点将图分为两部分的边集)的容量下限。这解释了为什么Ford-Fulkerson算法终止时流就是最大的。

20. R7. 网络流与匹配

网络流

网络流是一种算法模型,用于解决在图中从源点到汇点的最大流量问题。

最小价格流算法即弗朗绍夫算法可以解决这个问题。它的步骤如下:

  1. 给定一个流F,求出残留图G_F。
  2. 在残留图G_F中使用BFS算法查找从源点到汇点的路径。
  3. 沿着找到的路径增大流量,增量为该路径中的最小边容量。
  4. 得到新的流F’,更新残留图G_{F’} 。
  5. 重复第二步直到无法找到任何源点到汇点的路径为止。

弗朗绍夫算法的一个局限是,每次增广的路径可能很长,效率不高。

而额外克普拉算法可以解决这个问题。它每次都查找源点到汇点的最短路径来增广流。可以证明额外克普拉算法的时间复杂度是O(V^2E)。

网络流应用

网络流有两个重要的应用:

  1. 好处匹配问题:给定二部图,查找最大匹配。可以转化为网络流问题求解。

  2. 覆盖问题:给定有向图,求最少顶点覆盖所有边的子集。也可以转化为网络流问题求解。

额外克普拉算法可以高效地解决这两个问题。

21. 15. 线性规划:LP,减少,Simplex法

线性规划概述

线性规划是一种广泛应用于各种优化问题的技术,可以解决6.046课程和006课程前面看到的很多问题。

简单来说,线性规划采用线性限制条件和线性目标函数来描述最优化问题。它关注变量的值而不是变量本身,变量的值必须是实数。

一个政治广告例子

假设我们要通过广告买下一次选举,目标是以最小花费获得三个群体(城市、郊区、农村)超过一半票数的胜利。

我们有四个广告主题:修路、枪支控制、农业补贴、汽油税。根据每一群体对不同主题的支持程度,我们得到了每1美元广告花费能获取的票数表。

此外,我们还需要知道每一群体的人口数量,以计算需要获取的最小票数。

将问题转换为线性规划

我们将广告花费看作变量:$x_1$代表修路,$x_2$代表枪支控制,$x_3$代表农业补贴,$x_4$代表汽油税。

目标函数是最小化$x_1+x_2+x_3+x_4$

我们有三个不等式限制条件来表示在每个群体中需获胜:

  1. 对城市群体:-2$x_1$+8$x_2$+0$x_3$+10$x_4≥50000$

  2. 对郊区群体:5$x_1$+2$x_2$+0$x_3$+0$x_4≥100000$

  3. 对农村群体:3$x_1$-5$x_2$+10$x_3≥70000$

此外,$x_1,x_2,x_3,x_4≥0$,表示广告花费需为正数。

Simplex法求解

Simplex法是求解这类线性规划问题的经典算法。它以多项式时间内能求得最优解为概念框架,但实际运行时间可能是指数级的。

对这个例子,Simplex法可以找出使目标函数最小且满足所有约束的最优解:$x_1=20000/100,x_2=30000/100,x_3=0,x_4=40000/100$

总花费为$x_1+x_2+x_3+x_4=27000/100$,约等于$27000美元$。

22. 16. Complexity: P, NP, NP-completeness, Reductions

P问题

P问题是那些我们知道可以在多项式时间内解决的问题。多项式时间指的是问题规模n的某个常数次方。

NP问题

NP问题是那些不能确定在多项式时间内可以解决,但是可以在非确定性多项式时间内解决的问题。

在非确定性模型中,我们可以在常数时间内从多项式大小的选择集合中“猜测”一个答案。如果存在使问题答案为“是”的猜测,我们就能得到这样的猜测。

所以NP问题的答案可以通过“猜测+验证”的方式在多项式时间内得到。

3SAT问题

3SAT问题是NP完全问题的代表。

给定一个公式,公式由多个子句组成,每个子句有3个词元,词元是变量或者变量的否定形。问题就是问是否存在一个变量赋值使得整个公式为true。

3SAT问题可以用以下非确定性算法解决:

  1. 猜测每个变量的真值是true或者false
  2. 用常数时间猜测完每个变量的值
  3. 检验公式是否为true

如果公式为true,输出是;否则输出否。由于非确定模型会选择使问题答案为“是”的猜测,所以这个算法可以在多项式时间内解决3SAT问题。

证书和可验证性

可以把非确定性算法中的“猜测”看作是问题答案为“是”的“证书”。

如果一个问题存在多项式时间内的“验证算法”,这个算法可以在输入一个“certificate”后,多项式时间内判断这个证书是否正确证明问题答案为“是”,那么这个问题就是NP问题。

NP完全性

如果一个问题X满足:

  1. X属于NP
  2. 对任意NP问题Y,都存在多项式时间归约,可以将Y转换为X

那么问题X就是NP完全问题。

归约

如果可以在多项式时间内将问题X转换为问题Y,那么问题X至少也不比问题Y更难。

如果一个问题X可以多项式时间内归约任何NP问题,那么X就是NP困难。

如果一个问题X既属于NP,同时也是NP困难,那么这个问题X就是NP完全问题。

NP完全问题代表的是计算理论中已知难度最高的问题。解开NP完全问题,就意味着解决P!=NP这个开放问题。

23. R8. NP-Complete Problems

P问题是指可以在多项式时间内求解的决定性问题。NP问题是指在多项式时间内可以验证解是否正确,但未必能在多项式时间内找出解。

P是NP的子集。

NP难问题指的是至少与NP问题一样难解决的问题。

归约法可以用来证明一个问题的NP难性。如果已知问题A是NP难的,通过在多项式时间内将问题A转化成问题B,且问题A的答案等价于问题B的答案,那么问题B也是NP难的。

Hamilton回路问题要求在一个图中找出一个包含所有顶点且开始结束于同一顶点的回路。这是NP难问题。

Hamilton路径问题类似,但只要求包含所有顶点但无需开始结束于同一顶点。通过将一个顶点拆分成入边顶点和出边顶点,可以在多项式时间内将Hamilton回路问题转化成Hamilton路径问题,从而证明Hamilton路径问题也是NP难的。

clique问题要求在一个图中找出所有顶点都相连的完全子图。这是NP难问题。

独立集问题要求找出图中不相连的顶点集合。通过将是否存在k阶clique问题转化成是否存在大于等于k个元素的独立集问题,可以证明独立集问题也是NP难的。

24. 17. Complexity: Approximation Algorithms

1. NP完全问题

NP完全问题是一类计算复杂度很高的问题。对它们来说,暂时还没有找到多项式时间的确切解法。

2. 近似算法

近似算法用于在多项式时间内找到子optimal但满足某些性质的解。

近似率用Row_n表示,定义为算法产生的解C到最优解Copt之间的比值。

Row_n可以是常数,也可以是输入大小n的函数。如果Row_n随n增大而增大,表示近似质量会下降。

3. 近似算法工程

近似算法工程包括:

  1. 定义一个NP完全问题。

  2. 想出一个简单启发式策略。

  3. 证明该策略的近似率Row_n。

4. PTAS和FPTAS

PTAS(概率多项式时间近似方案)表示:

FPTAS(完全PTAS)表示时间复杂度与n和1/ε均为多项式。

5. 图覆盖问题

图覆盖问题是找到一个顶点集合,使其覆盖所有的边。它是一个NP完全问题。

简单贪心策略是选择每个邻边的一个顶点累加到解集中,可以得到2近似率。

25. 18. Complexity: Fixed-Parameter Algorithms

参数化问题

参数化问题就是将一个问题和一个参数组合在一起。例如连边覆盖问题,将给定的图G和一个非负整数k作为参数。问题是是否存在一个大小不大于k的连边覆盖。

参数通常是问题的某个规模大小等量值,例如数组的长度、图中的顶点数或边数等。

固定参数可解性

固定参数可解性意味着算法的时间复杂度为O(f(k)n^c),其中n是问题输入的总大小,k是参数,f为仅与k有关的函数,c为常数。

这样算法的时间复杂度虽然随着整体问题规模n成长而增加,但如果参数k足够小,时间复杂度仍然可控。

连边覆盖问题的粗暴蛮力算法

连边覆盖问题的粗暴蛮力算法:

  1. 枚举所有大小为k的顶点子集合。
  2. 对每个子集检查是否为连边覆盖。
  3. 如果找到满足条件的子集返回yes,否者返回no。

其时间复杂度为O(v^kE),依赖于参数k的指数项,且其中E和v为常数因子,因此不满足固定参数可解性定义。

总结

固定参数算法是一种寻找NP难问题近似解的方法。它旨在将算法时间复杂度的指数项约束在问题的某个参数k上,从而在参数k足够小时仍保持高效。连边覆盖问题给出的粗暴算法时间复杂度依赖总问题规模,不满足固定参数可解性定义。

26. R9. 近似算法:旅行商问题

最小生成树

加工MST得到Hamilton回路

MST回路的成本

与最优解的关系

改进到3/2近似

以上内容详细描述了使用MST构建TS问题的2近似算法以及其证明。

27. 19. 同步分布式算法:破坏对称性. 最短路径生成树

同步分布式网络模型描述了分布在网络中的多个节点(进程)在每一轮联合作业的计算模型。每个进程在每个轮回执行以下操作:

  1. 根据当前状态决定向每一个端口发送何种消息
  2. 消息被发送到相应的通道,并在这一轮结束时传递到接收进程
  3. 接收进程根据接收到的消息更新自己的状态

同步分布式算法的时间复杂度是指算法所需要的轮回数。算法的通信复杂度指发送的消息数或二进制位数。

破坏对称性问题(领导节点选择问题)的目的是在开始状态完全对称的多个进程中,选择唯一一个进程作为领导节点。

在完全连接的克利克图中(每个节点都直接连接到其他每个节点),如果进程起始状态完全相同且算法是确定性的,就无法破坏原始对称性选出唯一领导节点。因为每个进程都将采取相同行动。

同步分布式网络中最简单的问题之一是生成广度优先搜索树。每个节点通过广播消息,逐步获得整个网络的拓扑结构信息,并构建以自己为根的广度优先搜索树。

生成最短路径树问题与广度优先搜索树类似,但每个节点通过轮回交换信息,构建以自己为根的最短路径树,即从自己出发到每个其他节点的最短路径。

28. 20. 分布式算法中的无序通信:最短路径生成树

leader 选举

最大独立集算法

Luby 提出了一个基于随机性的最大独立集算法:

广度优先生成树

假设有一个根节点:

最短路径生成树

在带权图中,算法目标是构建以某节点为根的最短路径生成树:

异步算法

异步算法中,每轮不再同步:

本文总结了同步和异步分布式算法 constructing shortest-paths spanning trees。

29. R10. 分布式算法

1. 领导选举算法

本例中使用环形网络进行领导选举。每个节点随机生成一个ID。

算法原理:

  1. 每个节点首先生成一个唯一ID
  2. 将自己的ID传给相邻节点
  3. 接收到其他节点ID后,进行比较,找出最大ID的节点
  4. 最大ID节点output自己是领导

可以优化算法:

  1. 不必将小于自己ID的信息继续传播,提前筛选出不可能成为领导的节点
  2. 但此优化在极端情况下效果不明显,最坏复杂度还是O(n^2)

2. 二分法实现领导选举

原理:

  1. 将环形网络进行虚拟分割成左右两段
  2. 左右两段节点分别选举各自的领导
  3. 将左右两段领导进行比较,找出最大ID的节点即为总领导

问题:环形网络无法真实进行分割

3. 渐进式领导选举

原理:

  1. 每个节点初次随机产生ID
  2. 将ID和一次跳数传给相邻节点
  3. 中间节点减少跳数,并判断是否需要继续传播
  4. 到达终点后反向传回
  5. 成功传播ID表明ID可能是最大,继续扩大传播范围
  6. 否则该ID节点退出选举

每轮规模越来越大,最终剩下的最大ID节点为领导

算法时间复杂度为O(nlogn),消息复杂度为O(nlogn)

4. 节点数量统计

原理:

  1. 每个节点有自己的计数器初始为1
  2. 将计数器传给相邻节点
  3. 中间节点对收到的计数器求和,结果作为自己新的计数器
  4. 反复传播和求和,网络中所有的计数器值将同时收敛到节点总数

最终所有节点计数器的值即为网络中的总节点数量

30. 21. Cryptography: Hash Functions

哈希函数是将任意长度的输入映射为固定长度的输出。它应当是确定性的,公开的和看起来随机的。

哈希函数的定义

哈希函数将输入字符串映射到固定长度的二进制输出。输入可以是任意长度,输出通常为160位或者256位。

理想状态下的随机器

理想状态下,随机器可以将任意字符串作为输入,输出一个随机但确定的哈希值。它通过记录每次查询的输入输出来保持确定性。

哈希函数的属性

  1. 确定性:对同一个输入,每次计算都能得到同样的哈希值。

  2. 公开性:算法和任何状态都公开透明,没有任何密钥。

  3. 伪随机性:计算结果应当看起来完全随机,难以预测两个不同输入的哈希是否重合。

  4. 冲突抗力:极难找到有相同哈希值的两个不同输入,即找到冲突。

  5. 多项时间计算:与输入长度成正比,通常需要100轮或更多的计算来提升安全性。

常见哈希算法

MD4、MD5、SHA-1、SHA-3都是基于迭代和混合设计的汉明距离和置换BOX强密钥哈希算法。随着计算能力的增加,算法复杂度不断提高以达到更高的安全等级。

31. 22. Cryptography: Encryption

对称密钥加密

对称密钥加密算法假设存在一个密钥K。密钥K可以是128位或256位数字。

加密公式:

C = e(K, m)

其中:

解密公式:

m = d(K, C)

其中:

对称密钥加密需要满足可逆性,即使用相同的密钥K和解密函数d可以将加密文本C恢复成原明文m。

对称密钥加密使用可逆操作构建加密函数e和解密函数d,例如置换、xor等操作都是可逆的。

AES是一种广泛使用的对称加密标准算法。它使用排列置换和xor操作,加密和解密实现方式相同。

密钥交换

对称密钥加密需要解决如何让Alice和Bob安全地交换密钥K的问题。

例如可以用海盗密码箱模型:

非对称密钥加密

RSA算法是第一种保持安全的非对称加密算法。它使用大素数生成公钥和私钥,公钥用于加密,私钥用于解密。

密码学复杂度

如果不知道密钥,从加密文本推理明文应该是计算上很难的。这与NP完全问题的难度成正相关。一些密码系统就是根据NP完全问题如图着色问题设计的。

32. R11. Cryptography: More Primitives

数字签名

数字签名是用于验证消息正确性的机制。它由签名函数sign和验证函数verify构成。

sign函数使用密钥对消息进行签名,输出签名Σ。verify函数使用公钥、消息和签名,输出true或者false来接受或者拒绝这个签名。

我们希望数字签名具有以下属性:

  1. 正确性:如果Σ真的是通过sign函数产生的,那么verify应该输出true;否则输出false。

  2. 无法伪造:只有持有私钥的用户才能为消息签名。其他人即使看到了许多消息-签名对,也无法为之前未见过的消息生成验证通过的签名。

数字签名的实现

早期,研究人员曾提出使用公钥加密的逆函数来实现数字签名。以RSA为例:

签名函数sign: M^d mod n 验证函数verify: σ^e mod n = M

然而这个方案存在缺陷,它利用了RSA的乘法同态性,攻击者可以通过已知的消息-签名对生成新的签名。

为了解决这个问题,后来提出将消息哈希与RSA联合使用:

签名函数sign: hash(M)^d mod n 验证函数verify: σ^e mod n = hash(M)

这可以破坏乘法同态,然而由于哈希函数公开,仍可能存在其他攻击。

目前国际标准采用对哈希后的消息进行特定填充,再进行RSA运算。这保证功能但安全性无法严格证明。如何建立数字签名的理论基础仍是密码学研究的重要题目。

33. 23. 遗忘式缓存算法:中位数与矩阵

存储层次结构与访问时间

计算机中数据存储的层次结构包括:

随着数据存储位置的远离CPU,其访问时间也会显著增加。

阻塞技术

当CPU访问主存单个字时,实际读入的不仅是该字,而是一个较大的块,称为阻塞。阻塞大小一般设置为使单个字访问时间与读取整个阻塞的平均时间相当。例如对于硬盘,阻塞大小可达兆字节级。

阻塞技术能将单次访问的开销平均到整个阻塞上,从而降低单字访问时间。

空间局部性与时间局部性

高效利用缓存需要满足:

算法应保证数据访问顺序满足以上两点,使得缓存命中率更高。

遗忘式缓存算法

这类算法设计时不考虑实际缓存大小,但其访问顺序天然满足空间局部性和时间局部性,有利于缓存高效利用。后续会介绍一些代表性遗忘式缓存算法。

34. 24. Cache-Oblivious Algorithms: Searching & Sorting

模型概述

外部内存模型是一级隐形内存层次结构,CPU和Cache视为一个单元,内存传输速度极快。整个内存以块块的形式划分,块大小为b字节。问题规模n需要存储在磁盘上,磁盘也以块的形式划分。

算法只能读取或写入整个块,目的是最小化块内存传输次数。

Cache-oblivious模型中,算法不知道块大小b和Cache大小m,所有块读写都自动进行。当访问一个项时,如果其所在块不在Cache中会自动加载入Cache,如果Cache满会根据LRU策略淘汰最久未使用的块。

LRU近似分析定理

假设时间线可以划分为相对独立的相位。每个相位包含m/b个不同块的访问序列。

在每个相位中,LRU的块读取次数上限为m/b。OPT使用半数Cache(m/2)时,每个相位最少需要1/2m/b次块读取。

所以LRU算法在Cache大小m时,其块读取次数最多是OPT在Cache大小m/2时最优块读取次数的2倍。

查找算法

排序算法

后续课程