• 《算法导论》学习总结 — XX.第23章 最小生成树


    一、什么叫最小生成树

    一个无向连通图G=(V,E),最小生成树就是联结所有顶点的边的权值和最小时的子图T,此时T无回路且连接所有的顶点,所以它必须是棵树。

    二、为什么要研究最小生成树问题

    《算法导论》上举了电子线路设计的例子。而在经济学、生物学中也常应用最小生成树。

    三、如何求一个无向连通图的最小生成树

    《算法导论》中提取讲解了两种得到最小生成树的算法,一是Kruskal算法,另一种是Prim算法。这两种算法都使用了贪心策略。

    先说明几个概念:

    安全边:A是G的某最小生成树的子集,如果AU{(u,v)}仍是G的某最小生成树的子集,则(u,v)是安全边;

    割:无向图G=(V, E)对V一个划分(S, V-S);

    边(u,v)通过割(S,V-S):(u,v)一个顶点位于S中,另一个顶点位于V-S中;

    不妨害A的割:A中没有任意一条边通过该割;

    轻边(light edge):通过割的所有边中权值最小的(可能有多条)。

    生成最小生成树的基本代码结构是:

    [cpp] view plaincopy
     
    1. GENERIC-MST(G, w)  
    2.     A=空集  
    3.     while A does not form a spanning tree  
    4.         do find an edge(u,v) that is safe for A  
    5.             A=AU{(u,v)}  
    6.     return A  

    于是关键就在于找安全边。

    定理(《算法导论》中定理23.1)(以白话阐述):G=(V,E) 是个无向连通加权图。A 是 E 的一个子集,它包含于 G 的某个最小生成树中。设割 (S, V-S) 是G的任意一个不妨害A的割(就是说A中任何一条边的两个端点要么全在S中,要么全在V-S中),边(u,v)是通过割(S,V-s)的一条轻边(就是说(u,v)是所有端点分布于S和V-S的边中权值最小的),则(u,v)对集合A是安全的。

    推论:A是G=(V,E)的某个最小生成树的子集。G(A)=(V,A)是图G的一个森林(只有A集合中的边),C=(Vc, Ec)为G(A)的一个连通分支(森林中的树)。如果边(u,v)是连接C和G(A)中其他某连通分支的一条轻边,则(u,v)对集合A来说是安全的。

    1. Kruskal 算法

    [cpp] view plaincopy
     
    1. MST-KRUSKAL(G, w)  
    2.     A=空集  
    3.     for each vertex v∈V[G]  
    4.         do MAKE_SET(v)  
    5.     sort the edges of E into nondescreasing order by weight w  
    6.     for each edge(u,v)∈E, take in nondescreasing order by weight  
    7.         do if FIND-SET(u) != FIND-SET(v)                              // 如果u和v不在同一个连通分支中,就把(u,v)加入,由推论可知此边是安全的  
    8.             then A = AU{(u,v)}  
    9.                 UNION(u,v)  
    10.     return A  

    FIND-SET(u)是找出u所在的连通分支。

    Kruskal在全局中找权值最小的边,然后判断此边是否“合法”,进行取舍,直到遍历完所有的边。

    Kruskal算法的运行时间为O(ElgV)。

    2. Prim算法

    [cpp] view plaincopy
     
    1. MST-PRIM(G, w, r)  
    2.     for each u∈V[G]  
    3.         do key[u]=∞  
    4.           π(u) = NIL     // π(u)是u的前趋  
    5.     key[r] = 0  
    6.     Q=V[G]  
    7.     while Q != 空集  
    8.         do u=EXTRACT-MIN(Q)  
    9.             for each v∈Adj[u]  
    10.                 do if v∈Q and w(u,v)<key[v]  
    11.                     then π(v)=u  
    12.                           key[v] = w(u,v)       // 更新key[v]  

    Q是一个优先队列,key[v]是所有将v与树中某一顶点相连的边中的最小权值,若不存在这样的边,则k[v]=∞。

    Prim算法在局部寻找权值小的的边(此边必合法),直到遍历完所有的节点。

    Prim算法的运行时间为O(ElgV),与Kruskal算法渐近相等。

    Prim算法实际上使用了与Dijkstra算法同样的策略,维护了一个权值数组key,在迭代过程中不断的更新。

  • 相关阅读:
    redis基础
    docker日志清理脚本
    Hive修改表的所有者(Owner)
    User xxx does not have privileges for CREATETABLE的解决办法
    Spark读取Hive表中文显示乱码的解决办法
    Go语言之标识符与关键字
    Go语言之数据类型(三)
    bootstrapTable频繁向后台接口发请求
    框架整合疑云
    业务开发中-设计模式使用
  • 原文地址:https://www.cnblogs.com/downtjs/p/3424408.html
Copyright © 2020-2023  润新知