(10)图算法2: 最小生成树, 最短路径

最小生成树(MST)问题

  • 对象: 该问题总是针对连通无向图G = (V, E);
  • 总体算法
    • 这个算法理出大概的思路, 真正实现还分为点和边两种方式;
Generic-MST(G, w)  //w是权重函数
A = ∅
while A does not form a spanning tree 
    find an edge(u,v) that is safe for A
    A = A∪(u,v)
return A  //the minimum spanning tree
  • 总体性质: A是图G=(V, E)中E的在SpanningTree中的边的集合, 如果(u,v)是横跨切割(S, V-S)的轻量级边, 其中S是SpanningTree的意思, 那么边(u, v)对于集合A是安全的;

证明: (分类讨论)
--如果(u, v)在STree中, 那么命题得证(安全边才能被收入A中);
--如果(u,v)不在STree中, 因为u,v两个点都在STree中, 那么必然在A中已经通过某种路径相连, 这条路径加上(u,v)边会形成一个环路, 且此时至少有两条边跨越了切割, 一个是(u, v), 另外一个不妨写作(x, y). 若断掉(x,y)留下(u, v) 则此时T'.w = T.w + w(u, v) - w(x, y), 因为(u, v)是轻量级边, 所以T'.w <= T.w, 所以说(u,v)边是安全的;

prim算法

  • 思路: 并点, 不断找新生成树的切割面的最短边, 是典型的贪心算法;
MST-Prim(G, w, r)  //Graph, weight, root
for each u ∈ G.V
    u.key = ∞
    u.parent = null
r.key = 0
Q = G.V  //initiate a priority queue, 初始化结束
while Q != ∅:
    u = Extract-Min(Q)  //依据的是u.key是Q中最小的;
    for each v∈G.Adj[u]:  
        if v∈Q and w(u, v)<v.key:
            v.parent = u
            v.key = w(u, v)

Note: 获取MST的时候, 只要让每个非root的结点u输出(u, u.parent)边;

  • 时间复杂度分析:
    (1) 使用二维数组W存储weight, 一维数组V存储结点: 初始化操作Θ(V);Extra-Min操作V次, 每次耗时Θ(V), 所以这个环节是Θ(V^2); 对边的操作, 总共有E条边, 每次耗时都是Θ(1), 因此这个环节Θ(E); 总体耗时因此是Θ(V^2+E);
    (2) 使用二叉堆构建最小优先队列, Extract-Min耗时总共O(VlgV), 对边的操作需要做E次Decrease-Key, 耗时总共O(ElgV); 总体耗时因此为O((V+E)lgV), 因为E>=V-1总是成立, 因此可以写作O(ElgV);
    (3) 使用斐波那契堆实现最小优先队列Q的话, Extract-Min时间仍是lgV, 但是Decrease-Key下降到O(1), 因此总体上时间是O(VlgV+E), 小于O(ElgV).

Kruskal算法

  • 思路: 并边, 不断找整个图中最短边, 只要不构成环, 即相互不连通, 就并入, 也是贪心算法;.
MST-Kruskal(G, w)
A = ∅
for each u ∈ G.V
    Make-Set(u)
sort the edges of G.E as ascending order by weight w  //到此初始化完成
for edge(u, v) of the sorted edge sequence
    if Find-Set(u) != Find-Set(v):
        A = A ∪ { (u,v) }  //add edge to A;
        Union(u, v)  //combine two trees
return A  //A is the tree;
  • 时间复杂度分析: 排序消耗了O(ElgE), 在改进过的并查集中, Find-Set总共做了E次, 消耗O(E), 而Union总共做了V次, 消耗O(Vα(V)), 其中α(V)<=4, 可以看作O(lgV), 因此Union消耗了O(VlgV), 因此并边循环总共消耗O(E+VlgV), 显然dominator是排序操作, 因此总体来说 T = O(ElgE).

网路最短路径问题

  • 对象: 带权重的有向图G = (V, E); 注意和最小生成树的主要区别在于这是有向图!!!

共用方法

  • 初始化
Initialize-Single-Source(G, s)
for each vertex v ∈ G.V:
    v.d = ∞
    v.parent = null
s.d = 0
  • 松弛操作
Relax(u, v, w)
if v.d > u.d+w(u,v):
    v.d = u.d+w(u,v)
    v.parent = u

松弛定理: 对v点, 如果从s点到vi确实存在一条最短路径, 那么只要(s, v1), (v1, v2), ... (vi-1, vi)都被依次执行了relax操作, 那么vi.d = δ(s, vi). 该性质的成立与其他穿插在该序列中间的松弛操作无关.

  • 也就是说, 接下去的三个算法, 我们都要检查松弛定理是否能运用, 只要能运用, 算法是正确的;

1. 一般情况下的单源最短路径问题

  • 条件宽松: 允许出现负权重的环路;
Bellman-Ford(G, w, s) {
Initialize-Single-Source(G, s)
for i = 1~|V|-1:
    for each edge(u, v) ∈ G.E:  
        relax(u, v)
for each edge(u, v) ∈ G.E:
    if v.d >u.d+w(u, v):  //只要任何一条边仍能继续松弛, 那么说明有负权重的环路存在;
        return FALSE
return TRUE
}

正确性:
(1) 对某个点vi来说, 从s如果有一条最短路径, 那么在算法的第一个for循环, 到第i次循环, 该最短路径上的边(s, v1), (v1, v2)...(vi-1,vi)都将被松弛, 因此该算法能满足松弛定理, 使得vi.d = δ(s, vi);
(2) 最短路径上界定理: 对所有的最短路径来说, 最多只能有V-1条边, 否则将形成一个环. 因此Bellman-Ford算法只需要V-1轮全体relax就能cover到即使是最长的那条最短路径.

  • 时间复杂度分析: 主要时间消耗在V-1次轮的全体relax, 一共消耗了O(VE);

2. 有向无环图中的单源最短路径问题

  • 条件收紧: 允许使用负权重的边;
  • 借助拓扑序
DAG-Shortest-Path(G, w, s) {
topologically sort the vertices of G
Initialize-Single-Source(G, s)
for each vertex u taken by the sorted order:
    for each v of G.adj[u]:
        Relax(u, v, w)
}

正确性: 因为是按照拓扑序对所有的边进行松弛, 因此最短路径上的边(s, v1), (v1, v2)...(vi-1,vi)的边, 都会被依次松弛, 尽管中间可能穿插其他的松弛, 最后必然能实现vi.d = δ(s, vi)

迪杰斯特拉算法

  • 条件进一步收紧: 只允许非负权重的边;
  • 思路:
    对每个节点赋值∞, 每次都并一个距离最小的结点, 同时更新该结点的adj结点, 不断迭代;
Dijkstra(G, w, s) {
Initialize-Single-Source(G, s)
S = ∅
Q = G.V  //初始化完成
while Q != ∅:
    u = Extract-Min(Q)
    S = S∪{u}  //add u to S;
    for each vertex v of G.adj[u]:
        Relax(u, v, w)
}

正确性: Dij算法每次收入的点是当前Q中(尚未被染色的)满足d最小的点u, u.d此时已经满足被所有S中能到v的点给更新了. 那么为什么不担心之后加入S的点可能还会再减小v.d呢? 这是因为之后再加S的点, 必须经过之前的S的点, 这些后加点的v.d只可能在已有能连到它的点的基础上递增, 因为Dij算法要求图中没有负权重边.

  • 时间复杂度分析:
    (1) 使用二维数组W存储weight, 一维数组V存储结点: 初始化操作Θ(V);Extra-Min操作V次, 每次耗时Θ(V), 所以这个环节是Θ(V^2); 对边的操作, 总共有E条边, 每次耗时都是Θ(1), 因此这个环节Θ(E); 总体耗时因此是Θ(V^2+E); (注意到V-1<=E<=V^2, 因此可以写成Θ(V^2)).
    (2) 使用二叉堆构建最小优先队列, Extract-Min耗时总共O(VlgV), 对边的操作需要做E次Decrease-Key, 耗时总共O(ElgV); 总体耗时因此为O((V+E)lgV), 因为E>=V-1总是成立, 因此可以写作O(ElgV);
    (3) 使用斐波那契堆实现最小优先队列Q的话, Extract-Min时间仍是lgV, 但是Decrease-Key下降到O(1), 因此总体上时间是O(VlgV+E), 小于O(ElgV).
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容