Greedy Algorithm
- 算法设计:不存在万金油——针对问题选择合适的算法
- 定义:不断地进行“短视地”决策,期望到最后一切都正常进行
- 举例:Dijkstra最短路算法
- 与分治法的对比
- 很容易想到比较容易的贪心策略
- 时间复杂度分析更加容易(不需要主分析方法)
- 难以证明正确性
- 一般情况下,想到的贪心策略很容易是错误的
- 正确性证明
- 归纳法
- 变量交换法(反证)
- 创造性的思路
- 应用:最佳caching
- 选择最理想的位置,将cache中原有数据去除装入新的内容——效率最高?
- “未来最远”算法
- 实际:最少最近使用LRU优先替换
- 调度问题
- 一个共享资源,很多个任务
- 如何确定任务的顺序?
- 任务的组成
- 非负权重 w j w_j wj(优先级)
- 运行时长 l j l_j lj
- 任务的完成时间 c j c_j cj,从开始到完成该任务的总共用时
- 目标函数:最小化完成时间的加权和 min ∑ j w j c j \min \sum_j w_j c_j min∑jwjcj
- 算法:按照 w j e j \frac {w_j} {e_j} ejwj进行排序(从大到小)
Minimum Spanning Trees
- 输入:无向图
G
=
(
V
,
E
)
G = (V, E)
G=(V,E),以及针对每一条边的连结代价
c
e
c_e
ce
- 假设使用邻接表的表示方法
- 输出:最小代价的连结所有节点的生成树
T
T
T
- 树——没有任何环
- 子图 ( V , T ) (V, T) (V,T)是连通的
- 两个假设
- 图
G
G
G是连通的
- 否则不存在生成树
- 很容易通过BFS的算法检查成立
- 每个代价
c
e
c_e
ce都是不同的
- 即使存在相同代价,算法也是成立的
- 图
G
G
G是连通的
- Prim算法
- 每次纳入一个节点
- 贪心策略:选择当前满足条件的最小代价边
- 初始化 X = [ s ] X = [s] X=[s], s s s为随便选择的一点, T = ∅ T = \emptyset T=∅
- 迭代以下步骤,直到
X
=
V
X = V
X=V
- 选择其中一个节点不在 X X X的最小代价边 e = ( u , v ) e=(u, v) e=(u,v)
- 将 e e e加入 T T T
- 将 v v v加入 X X X
- 时间复杂度:堆实现 O ( m log n ) O(m \log n) O(mlogn)
- Prim算法证明思路
- 步骤1:Prim算法会产生一棵生成树
- 割:将 G = ( V , E ) G=(V, E) G=(V,E)的点集划分为两个非空集合
- 对一个给定图, n n n个节点,会产生 2 n 2^n 2n中不同的割(实际更少,因为不允许空点集)
- 空割引理:如果一个图是不连通的,当且仅当存在一个割没有交边
- 双交边引理:如果存在一个环
C
C
C,其中一条边成为了割的交边,那么必然存在
C
C
C中的另一条边作为交边
- 推论:如果某个割只有唯一的交边,图中不存在环
- 证明
- 算法维护的不变量: X X X生成 T T T
- 直到 X = V X = V X=V才会停止迭代——否则会出现空割情形(非连通图)
- T T T中不存在环—— T T T中所有边由 X X X生成,只有新加入的边为交边(唯一交边)
- 步骤2:Prim算法会产生一棵最小生成树
- 割性质:存在一条边,是割 ( A , B ) (A,B) (A,B)的最低代价交边,那么这条边属于该图的最小生成树
- 在所有边代价不同的假设下,MST是唯一的
- 割性质是正确的(需要后续证明)
- 由于每一条纳入到Prim算法生成树的边都满足割性质
- 对于最后生成的树,其必然是MST
- 步骤1:Prim算法会产生一棵生成树
- 直接实现:按点迭代,每次迭代遍历边, O ( M N ) O(MN) O(MN)
- 堆实现
- 使用堆维护最小代价边
- 注意选择的边还要满足交边
- 不变量1:堆保存尚未生成的顶带你
- 不变量2:键值为其作为顶点的交边的最小代价
- 当移入顶点
v
v
v时
- 检查所有另一个端点仍在 V − X V - X V−X中的边 ( v , w ) (v,w) (v,w)
- 从堆中移出对应的端点 w w w
- 重新计算 k e y [ w ] = min { k e y [ w ] , C v w } key[w] = \min \{ key[w], C_{vw}\} key[w]=min{key[w],Cvw}
- 重新插入 w w w
- 当移入顶点
v
v
v时
- 初始化时间(扫描所有边计算键值 + 插入到堆中): O ( m + n log n ) = O ( m log n ) O(m + n \log n) = O(m \log n) O(m+nlogn)=O(mlogn)
- 时间复杂度: O ( m log n ) O(m \log n) O(mlogn)