图论

来源:互联网 发布:淘宝客软件哪个好 编辑:程序博客网 时间:2024/05/01 04:41

抄自http://nthucad.cs.nthu.edu.tw/~yyliu/personal/nou/04ds/dijkstra.html

代克思托演算法 (Dijkstra's algorithm)


Dijkstra's algorithm 是以某一節點為出發點,計算從該節點出發到所有其他節點 的最短路徑。

首先以某一節點當作出發點,在與其相連且尚未被選取的節點裡,選擇加入離出發點距離 最短的節點,並且透過新增的節點更新到達其他節點的距離。 如此重覆加入新節點,直到所有的節點都被加入為止。

※ 請把課本 P.317頁,圖11-20 e 到 c 邊的權重,從8 更正為 6。


  1. 起始。


    步驟       即將新增 abcdefg 1-------aps. "-" 代表目前無法到達,所以距離為無限大,即課本的 无穷大
  2. 從 a 開始,與 a 相連的節點有兩個 (f, b)。
    a 與 a 的距離為 0。 (新增 0)
    f 與 a 的距離為 3。 (新增 3)
    b 與 a 的距離為 32。 (新增 32)


    與 a 距離最短的節點為 f。
    步驟       即將新增 abcdefg 1-------a2032---3-fps. 加入後的節點將著色顯示。
  3. 加入 f 後,與 f 相連的節點有兩個 (c, b)。
    透過 f , c 與 a 的距離為 3 + 2 = 5。 (新增 5)
    透過 f , b 與 a 的距離為 3 + 7 = 10。 (取代 32)


    與 a 距離最短的節點為 c。
    步驟       即將新增 abcdefg 1-------a2032---3-f30105--3-c
  4. 加入 c 後,與 c 相連的節點有三個 (b, e, g)。
    透過 c , b 與 a 的距離為 5 + 21 = 26。 (保留 10)
    透過 c , e 與 a 的距離為 5 + 6 = 11。 (新增 11)
    透過 c , g 與 a 的距離為 5 + 11 = 16。 (新增 16)


    與 a 距離最短的節點為 b。
    步驟       即將新增 abcdefg 1-------a2032---3-f30105--3-c40105-11316b
  5. 加入 b 後,與 b 相連的節點有一個 (e)。
    透過 b , e 與 a 的距離為 10 + 12 = 22。 (保留 11)


    與 a 距離最短的節點為 e。
    步驟       即將新增 abcdefg 1-------a2032---3-f30105--3-c40105-11316b50105-11316e
  6. 加入 e 後,與 e 相連的節點有一個 (d)。
    透過 e , d 與 a 的距離為 11 + 13 = 24。 (新增 24)


    與 a 距離最短的節點為 g。
    步驟       即將新增 abcdefg 1-------a2032---3-f30105--3-c40105-11316b50105-11316e601052411316g
  7. 加入 g 後,與 g 相連的節點有一個 (d)。
    透過 g , d 與 a 的距離為 16 + 9 = 25。 (保留 24)


    與 a 距離最短的節點為 d。
    步驟       即將新增 abcdefg 1-------a2032---3-f30105--3-c40105-11316b50105-11316e601052411316g701052411316d
  8. 加入 d 後,所有結點皆已加入,所以Dijkstra's algorithm完成。


    所有節點與 a 的最短距離如下:
     abcdefga01052411316

 

抄自http://www.ahhf45.com/info/Data_Structures_and_Algorithms/algorithm/commonalg/graph/mst/chapter2.htm

最小生成树

Kruskal算法和Prim算法

本节所阐述的两种最小生成树算法是上节所介绍的一般算法的细化。每个算法均采用一特定规则来确定GENERIC-MST算法第3行所描述的安全边;在Kruskal算法中,集合A是一森林,加大集合A中的安全边总是图中连结两不同连通支的最小权边。在Prim算法中,集合A仅形成单棵树。添加入集合A的安全边总是连结树与一非树结点的最小权边。

Kruskal算法

Kruskal算法是直接基于上一节中给出的一般最小生成树算法的基础之上的。该算法找出森林中连结任意两棵树的所有边中具有最小权值的边(u,v)作为安全边,并把它添加到正在生长的森林中。设C1和C2表示边(u,v)连结的两棵树。因为(u,v)必是连C1和其他某棵树的一条轻边,所以由推论2可知(u,v)对C1是安全边。Kruskal算法同时也是一种贪心算法,因为算法每一步添加到森林中的边的权值都尽可能小。

Kruskal算法的实现类似于计算连通支的算法。它使用了分离集合数据结构以保持数个互相分离的元素集合。每一集合包含当前森林中某个树的结点,操作FIND-SET(u)返回包含u的集合的一个代表元素,因此我们可以通过FIND-SET(v)来确定两结点u和v是否属于同一棵树,通过操作UNION来完成树与树的联结。

 MST-KRUSKAL(G,w)1. A←Æ2. for 每个结点v V[G] 3.   do MAKE-SET(v)4. 根据权w的非递减顺序对E的边进行排序5. for 每条边(u,v)ÎE,按权的非递减次序6.   do if FIND-SET(u) ≠ FIND-SET(v)7.        then A←A∪{(u,v)}8.             UNION(u,v)9  return A

Kruskal算法的工作流程如图4所示。阴影覆盖的边属于正在生成的森林A。算法按权的大小顺序考察各边。箭头指向算法每一步所考察到的边。第1-3行初始化集合A为空集并建立|V|棵树,每裸树包含图的一个结点。在第4行中根据其权值非递减次序对E的边进行排序。在第5-8行的for循环中首先检查对每条边(u,v)其端点u和v是否属于同一棵树。如果是,则把(u,v)加入森林就会形成回路,所以这时放弃边(u,v)。如果不是,则两结点分属不同的树,由第7行把边加入集合A中,第8行对两棵树中的结点进行归并。

(a)

(b)

(c)

(d)

(e)

(f)

(g)

(h)

(i)

(j)

(k)

(l)

(m)

(n)

图4 Kruskal算法在图1所示的图上的执行流程

Kruskal算法在图G=(V,E)上的运行时间取决于分离集合这一数据结构如何实现。我们采用在分离集合中描述的按行结合和通路压缩的启发式方法来实现分离集合森林的结构,这是由于从渐近意义上来说,这是目前所知的最快的实现方法。初始化需占用时间O(V),第4行中对边进行排序需要的运行时间为O(E㏒E);对分离集的森林要进行O(E)次操作,总共需要时间为O(Ea(E,V)),其中a函数为Ackerman函数的反函数。因为a(E,V)=O(㏒E),所以KruskaI算法的全部运行时间为O(E㏒E)。

Prim算法

正如Kruskal算法一样,Prim算法也是第上一节中讨论的一般最小生成树算法的特例。Prim算法的执行非常类似于寻找图的最短通路的Dijkstra算法。Prim算法的特点是集合A中的边总是只形成单棵树。如图5所示,阴影覆盖的边属于正在生成的树,树中的结点为黑色。在算法的每一步,树中的结点确定了图的一个割,并且通过该割的轻边被加进树中。树从任意根结点r开始形成并逐渐生长直至该树跨越了V中的所有结点。在每一步,连接A中某结点到V-A中某结点的轻边被加入到树中,由推论2,该规则仅加大对A安全的边,因此当算法终止时,A中的边就成为一棵最小生成树。因为每次添加到树中的边都是使树的权尽可能小的边,因此上述策略也是贪心的。

有效实现Prim算法的关键是设法较容易地选择一条新的边添加到由A的边所形成的树中,在下面的伪代码中,算法的输入是连通图G和将生成的最小生成树的根r。在算法执行过程中,不在树中的所有结点都驻留于优先级基于key域的队列Q中。对每个结点v,key[v]是连接v到树中结点的边所具有的最小权值;按常规,若不存在这样的边则key[v]=∞。域p[v]说明树中v的“父母”。在算法执行中,GENERIC-MST的集合A隐含地满足:

A={(v,p[v])|vÎV-{r}-Q}

当算法终止时,优先队列Q为空,因此G的最小生成树A满足:

A={(v,p[v])|vÎ V-{r}}

(a)

(b)

(c)

(d)

(e)

(f)

(g)

(h)

(i)

 

图5 Prim算法在图1所示的图上的执行流程

 MST-PRIM(G,w,r)1. Q←V[G]2. for 每个uÎQ3.   do key[u]←∞4. key[r]←05. p[r]←NIL6. while Q≠Æ7.   do u←EXTRACT-MIN(Q)8.      for 每个vÎAdj[u]9.        do if vÎQ and w(u,v)<key[v]10.            then p[v]←u11.                 key[v]←w(u,v)

Prim算法的工作流程如图5所示。第1-4行初始化优先队列Q使其包含所有结点,置每个结点的key域为∞(除根r以外),r的key域被置为0。第5行初始化p[r]的值为NIL,这是由于r没有父母。在整个算法中,集合V-Q包含正在生长的树中的结点。第7行识别出与通过割(V-Q,Q)的一条轻边相关联的结点uÎQ(第一次迭代例外,根据第4行这时u=r)。从集合Q中去掉u后把它加入到树的结点集合V-Q中。第8-11行对与u邻接且不在树中的每个结点v的key域和p域进行更新,这样的更新保证key[v]=w(v,p[v])且(v,p[v])是连接v到树中某结点的一条轻边。

Prim算法的性能取决于我们如何实现优先队列Q。若用二叉堆来实现Q,我们可以使用过程BUILD-HEAP来实现第1-4行的初始化部分,其运行时间为O(V)。循环需执行|V|次,且由于每次EXTRACT-MIN操作需要O(㏒V)的时间,所以对EXTRACT-MIN的全部调用所占用的时间为O(V㏒V)。第8-11行的for循环总共要执行O(E)次,这是因为所有邻接表的长度和为2|E|。在for循环内部,第9行对队列Q的成员条件进行测试可以在常数时间内完成,这是由于我们可以为每个结点空出1位(bit)的空间来记录该结点是否在队列Q中,并在该结点被移出队列时随时对该位进行更新。第11行的赋值语句隐含一个对堆进行的DECREASE-KEY操作,该操作在堆上可用0(㏒V)的时间完成。因此,Prim算法的整个运行时间为O(V㏒V+E㏒V)=O(E㏒V),从渐近意义上来说,它和实现Kruskal算法的运行时间相同。

通过使用Fibonacci堆,Prim算法的渐近意义上的运行时间可得到改进。在Fibonacci堆中我们己经说明,如果|V|个元素被组织成Fibonacci堆,可以在O(㏒V)的平摊时间内完成EXTRACT-MIN操作,在O(1)的平摊时间里完成DECREASE-KEY操作(为实现第11行的代码),因此,若我们用Fibonacci堆来实现优先队列Q,Prim算法的运行时间可以改进为O(E+V㏒V)

原创粉丝点击