數據結構與算法——圖形結構(七)

  

  

數據結構——圖node

一、圖的基本概念python

二、圖的數據表示法算法

2.1 鄰接矩陣表示法數組

  假設一個圖A有n個頂點,咱們以n*n的二維矩陣列來表示它,這個二維矩陣就是該圖的鄰接矩陣,此矩陣的定義以下:對於一個圖G=(V,E),假設有n個頂點,n>=1,則能夠將n個頂點的圖使用一個n*n的二維矩陣來表示,其中A(i,j)=1,則表示圖中有一條邊(Vi,Vj)存在,反之,A(i,j)=0,則不存在(Vi,Vj)。
網絡

  相關特性說明以下:數據結構

  (1)對無向圖而言,鄰接矩陣必定是對稱的,而對角線必定爲0。有向圖則不必定如此。app

  (2)在無向圖中,任意結點 i 的度數就是第 i 行全部元素的和。在有向圖中,結點 i 的出度就是第 i 行全部元素之和;結點 j 的入度就是第 j 列全部元素之和。函數

  (3)用鄰接矩陣法表示圖共須要 n^2 個單位空間,因爲無向圖的鄰接矩陣具備對稱關係的,扣除對角線所有爲 0 外,僅須要存儲上三角形數據便可,所以僅須要n(n-1)/2。工具

  接下來,咱們看一個實際的例子,以鄰接矩陣來表示無向圖,無向圖以下圖所示:spa

  該無向圖的鄰接矩陣表示爲:

  咱們接下來使用程序來建立鄰接矩陣表示這個無向圖,該程序使用Python2實現:

 1 import numpy as np
 2 
 3 #返回某個頂點在頂點列表中的位置索引
 4 def find_index(node, node_list):
 5     for i in range(len(node_list)):
 6         if node == node_list[i]:
 7             return i
 8     return -1
 9 
10 #無向圖的輸入,採用二維數組
11 data = [[1, 2], [1, 5], [2, 3], [2, 4], [3, 4], [3, 5],[4, 5]]
12 #頂點列表
13 vertex_list = [1, 2, 3, 4, 5]
14 #建立一個鄰接矩陣
15 Adj_matrix = np.zeros((5, 5), dtype=int)
16 #開始遍歷圖數據,生成鄰接矩陣。
17 for i in range(len(data)):
18     temp1 = find_index(data[i][0], vertex_list)   #找到頂點在頂點列表中的索引
19     temp2 = find_index(data[i][1], vertex_list)
20     Adj_matrix[temp1][temp2] = 1                    #將有邊的點出填入1
21     Adj_matrix[temp2][temp1] = 1                    # 將有邊的點出填入1
22 #輸出鄰接矩陣
23 for i in range(len(Adj_matrix)):
24     for j in range(len(Adj_matrix[0])):
25         print Adj_matrix[i][j],                     #python2的用法,在後面加「,」表示不換行。
26     print ""

運行的結果以下:

      0 1 0 0 1
      1 0 1 1 0
      0 1 0 1 1
      0 1 1 0 1
      1 0 1 1 0

 

 

 

  下面咱們再看一個有向圖的例子,以鄰接矩陣來表示有向圖,有向圖以下圖所示:

  該有向圖的鄰接矩陣表示爲:

  咱們接下來使用程序來建立鄰接矩陣表示這個有向圖,該程序使用Python2實現:

 1 import numpy as np
 2 
 3 #返回某個頂點在頂點列表中的位置索引
 4 def find_index(node, node_list):
 5     for i in range(len(node_list)):
 6         if node == node_list[i]:
 7             return i
 8     return -1
 9 
10 #有向圖的輸入,採用二維數組
11 data = [[1, 2], [2, 1], [2, 3], [2, 4], [4, 3], [4, 1]]
12 #頂點列表
13 vertex_list = [1, 2, 3, 4]
14 #建立一個鄰接矩陣
15 Adj_matrix = np.zeros((5, 5), dtype=int)
16 #開始遍歷數據,生成鄰接矩陣。
17 for i in range(len(data)):
18     temp1 = find_index(data[i][0], vertex_list)   #找到頂點在頂點列表中的索引
19     temp2 = find_index(data[i][1], vertex_list)
20     Adj_matrix[temp1][temp2] = 1                    #將有邊的點出填入1
21 #輸出鄰接矩陣
22 for i in range(len(Adj_matrix)):
23     for j in range(len(Adj_matrix[0])):
24         print Adj_matrix[i][j],                     #python2的用法,在後面加「,」表示不換行。
25     print ""
  
運行的結果以下:

      0 1 0 0 0
      1 0 1 1 0
      0 0 0 0 0
      1 0 1 0 0
      0 0 0 0 0

 

2.2 鄰接表法

  前面所介紹的鄰接矩陣法,優勢是憑藉着矩陣的運算又許多特別的應用。要在圖中加入新邊時,這個表示法的插入和刪除至關簡易。不過還要考慮到稀疏矩陣空間的浪費問題,另外,若是要計算全部頂點的度,其時間複雜度爲O(n^2)。

  所以能夠考慮更有效的方法,就是鄰接表法(Adjacency List)。這種表示法就是將一個n行的鄰接矩陣表示成n個鏈表,這種作法和鄰接矩陣相比較節省空間,如計算全部頂點的度時,其時間複雜度爲O(n+e),缺點是:例如有新邊加入圖中或者從圖中刪除邊時,就要修改相關的連接,較爲麻煩費時。

  首先,將圖的n個頂點做爲n個鏈表頭,每一個鏈表中的結點表示它們和鏈表頭結點之間有邊相連。每一個結點的數據結構以下:

 

1 class list_node(object):           #定義一個結點類
2     def __init__(self):            #構造函數
3         self.data = 0              #結點的數據域
4         self.next = None           #結點的指針域

  在無向圖中,由於對稱關係,如有n個頂點、m個邊,則造成n個鏈表頭,2m個結點。若在有向圖中,則有n個鏈表頭以及m個結點,所以在鄰接表中,求全部頂點的度所需的時間複雜度爲O(n+m)。

   接下來,咱們看一個實際的例子,以鄰接表來表示無向圖,無向圖以下圖所示:

   首先根據上圖可知,由於5個頂點使用5個鏈表頭,V1鏈表表明頂點1,與頂點1相鄰的是頂點2和頂點5,以此類推,該無向圖鄰接表表示以下:

  咱們接下來使用程序來建立鄰接矩陣表示這個有向圖,該程序使用Python2實現:

 1 class list_node(object):           #定義一個結點類
 2     def __init__(self):            #構造函數
 3         self.data = 0              #結點的數據域
 4         self.next = None           #結點的指針域
 5 
 6 #頂點列表
 7 vertex_list = [1, 2, 3, 4, 5]
 8 head = [list_node] * len(vertex_list)        #聲明一個結點類型的列表
 9 newnode = list_node()
10 data = [[1, 2], [2, 1], [1, 5], [5, 1], [2, 3], [3, 2], [2, 4], [4, 2], [3, 4], [4, 3], [3, 5], [5, 3], [4, 5], [5, 4]]
11 print len(data)
12 print "圖的鄰接表的內容"
13 print "-----------------------------------------------------"
14 
15 for i in range(len(vertex_list)):            #生成頭結點,以五個頂點做爲頭結點
16     head[i].data = vertex_list[i]             #分別將頂點列表的各個頂點元素存入頭結點的數據域中
17     head[i].next = None                       #頭結點指針域指向None
18     print "頂點 %d =>" % vertex_list[i],       #打印頭結點信息
19     for j in range(len(data)):                #遍歷整個傳入的圖的數據,經過它建立圖的鄰接鏈表結構
20         if data[j][0] == vertex_list[i]:       #輸入數據中的起始結點等於頂點,就在終止結點加入到該頂點的鄰接鏈表中去。
21             newnode.data = data[j][1]           #爲終止頂點建立一個結點信息,並將其元素值加入到數據域中
22             newnode.next = head[i].next         #採用頭部插入的方式,插入該結點
23             head[i].next = newnode              #這是頭部插入法
24             print "[%d] " %newnode.data,       #循環打印屬於某一頭結點鄰接結點的全部數據元素。
25     print ""                                    #表示換行

運行結果以下:

      -----------------------------------------------------
      頂點 1 => [2] [5]
      頂點 2 => [1] [3] [4]
      頂點 3 => [2] [4] [5]
      頂點 4 => [2] [3] [5]
      頂點 5 => [1] [3] [4]

 

 2.3 圖的特殊表示法(利用python的基本數據結構類型)

  圖是一種重要的數據結構,它能夠表明各類結構和系統,從運輸網絡到通訊網絡,從細胞核中的蛋白質相互做用到人類在線交互。圖是由頂點的有窮非空集合和頂點之間邊的集合組成,一般表示爲:G(V,E),其中,G表示一個圖,V是圖G中的頂點的集合,E是圖G中邊的集合。以下圖:

  對於圖結構的實現來講,最直觀的方式之一就是使用鄰接表來表示。基本上就是針對每個節點設置一個鄰接表,而對於鄰接表的實現方式能夠不一樣,針對python的特色以及內置的數據結構,可使用列表、集合和字典來實現。

  (1)鄰接集合

    第一種實現鄰接表的方式是:針對每一個結點設置一個鄰居集合,在python中就是set。 

 1 a, b, c, d, e, f, g, h = range(8)
 2 Adj_set = [
 3             {b, c, d, e, f},
 4             {c, e},
 5             {d},
 6             {e},
 7             {f},
 8             {c, g, h},
 9             {f, h},
10             {f, g}
11 ]
12 #列表中的每一個集合是每一個結點的鄰接點集
13 
14 print b in Adj_set[a]         #結點b是不是結點a的鄰居結點
15 print len(Adj_set[a])         #結點a的出度

運行結果以下:

      True
      5

 

  (2)鄰接列表

  第二種實現鄰接表的方式是:針對每一個結點設置一個鄰居列表,在python中就是list。
 1 a, b, c, d, e, f, g, h = range(8)
 2 Adj_list = [
 3             [b, c, d, e, f],
 4             [c, e],
 5             [d],
 6             [e],
 7             [f],
 8             [c, g, h],
 9             [f, h],
10             [f, g]
11 ]
12 
13 print b in Adj_list[a]         #結點b是不是結點a的鄰居結點
14 print len(Adj_list[a])         #結點a的出度

運行結果以下:
      True
      5

 

 

  (3)加權的鄰接字典

  使用字典類型來代替集合或列表來表示鄰接表。在字典類型中,每一個鄰居節點都會有一個鍵和一個額外的值,用於表示與其鄰居節點(或出邊)之間的關聯性,如邊的權重。

 

 1 a, b, c, d, e, f, g, h = range(8)
 2 Adj_dict_weight = [
 3                       {b: 2, c: 1, d: 3, e: 9, f: 4},
 4                       {c: 4, e: 3},
 5                       {d: 8},
 6                       {e: 7},
 7                       {f: 5},
 8                       {c: 2, g: 2, h: 2},
 9                       {f: 1, h: 6},
10                       {f: 9, g: 8}
11 ]
12 
13 
14 print b in Adj_dict_weight[a]         #結點b是不是結點a的鄰居結點
15 print len(Adj_dict_weight[a])         #結點a的出度
16 print Adj_dict_weight[a][b]           #邊(a,b)的權重

運行結果以下:
      True
      5
      2

 

 

 

  (4)鄰接集字典

  以上圖的表示方法都使用了list類型,其實,也可使用字典結構dict和集合結構set的嵌套來實現。 
 1 Adj_set_dict = {'a':set('bcdef'),
 2      'b':set('ce'),
 3      'c':set('d'),
 4      'd':set('e'),
 5      'e':set('f'),
 6      'f':set('cgh'),
 7      'g':set('fh'),
 8      'h':set('fg')
 9 }
10 
11 print Adj_set_dict["a"]            #節點a的鄰居節點
12 print "b" in Adj_set_dict["a"]     #節點b是不是節點a的鄰居節點

運行結果以下:
      set(['c', 'b', 'e', 'd', 'f'])

      True

 

 

  (5)嵌套字典(最重要*****)

  也可使用嵌套字典的方式來實現加權圖。
1 Nest_dict = {'a':{'b':2, 'c':1, 'd':3, 'e':9, 'f':4},
2      'b':{'c':4, 'e':3},
3      'c':{'d':8},
4      'd':{'e':7},
5      'e':{'f':5},
6      'f':{'c':2, 'g':2, 'h':2},
7      'g':{'f':1, 'h':6},
8      'h':{'f':9, 'g':8}
9 }

 

三、圖的遍歷

  圖遍歷又稱圖的遍歷,屬於數據結構中的重要內容。它指的是從圖中的任一頂點出發,對圖中的全部頂點訪問一次且只訪問一次。圖的遍歷操做和樹的遍歷操做功能類似。圖的遍歷是圖的一種基本操做,圖的許多其它操做都是創建在遍歷操做的基礎之上。

  因爲圖結構自己的複雜性,因此圖的遍歷操做也較複雜,主要表如今如下四個方面:

  (a)在圖結構中,沒有一個「天然」的首結點,圖中任意一個頂點均可做爲第一個被訪問的結點。

  (b)在非連通圖中,從一個頂點出發,只可以訪問它所在的連通份量上的全部頂點,所以,還需考慮如何選取下一個出發點以訪問圖中其他的連通份量。

  (c)在圖結構中,若是有迴路存在,那麼一個頂點被訪問以後,有可能沿迴路又回到該頂點。

  (d)在圖結構中,一個頂點能夠和其它多個頂點相連,當這樣的頂點訪問事後,存在如何選取下一個要訪問的頂點的問題。

(1)深度優先遍歷(DFS)

  深度優先遍歷也稱爲深度優先搜索(Depth First Search),它相似於樹的先序遍歷,具體定義以下:假設初始狀態是圖中全部頂點均未被訪問,則從某個頂點v出發,首先訪問該頂點,而後依次從它的各個未被訪問的鄰接點出發深度優先搜索遍歷圖,直至圖中全部和v有路徑相通的頂點都被訪問到。 若此時尚有其餘頂點未被訪問到,則另選一個未被訪問的頂點做起始點,重複上述過程,直至圖中全部頂點都被訪問到爲止。

  深度優先遍歷的定義就是一個遞歸的過程,每次都以當前節點的第一個不曾訪問過的鄰接點進行深度優先遍歷的過程。所以使用遞歸函數實現該算法是最直觀的實現方式,但因爲遞歸的過程是函數棧累積的過程,若是節點數較多,容易形成函數棧的溢出而致使程序崩潰,所以正常生產環境通常會使用一個棧結構(Stack)來存放遍歷的節點以模擬函數棧的調用狀況,以此避免遞歸的缺陷。
  接下來,咱們將如下面的無向圖展現深度優先遍歷:

  所以,使用一個棧(Stack)輔助實現深度優先遍歷(DFS)代碼以下(python 2.7):
 1 #建立一個圖
 2 Graph = {}
 3 Graph['A'] = ['B', 'C', 'D']
 4 Graph['B'] = ['A', 'E']
 5 Graph['C'] = ['A', 'F']
 6 Graph['D'] = ['A', 'G', 'H']
 7 Graph['E'] = ['B', 'F']
 8 Graph['F'] = ['E', 'C']
 9 Graph['G'] = ['D', 'H', 'I']
10 Graph['H'] = ['G', 'D']
11 Graph['I'] = ['G']
12 
13 
14 #使用堆棧來實現深度優先遍歷
15 def DFSTraverse(G, start):
16     stack = []                           #初始化一個堆棧
17     visited = set()                      #初始化一個訪問過的節點集合
18     stack.append(start)                  #將起始結點入棧
19     while stack:                         #若是棧不爲空,進入循環
20         node = stack.pop()               #棧頂元素出棧
21         if node in visited:              #判斷棧頂元素是否被訪問過
22             continue                     #元素被訪問過,跳出循環,查看棧內的其餘元素
23         else:                             #棧頂元素未被訪問
24             print node,                   #訪問棧頂元素
25             visited.add(node)             #將其加入訪問過的集合
26             for adj in G[node]:           #將該元素的鄰居節點加入到棧中去
27                 if adj not in visited:
28                     stack.append(adj)
29     print "\n"
30 
31 
32 if __name__ == '__main__':
33 
34     print "深度優先搜索結果:"
35     DFSTraverse(Graph, 'A')

運行結果以下:

      深度優先搜索結果:
      A D H G I C F E B

 

(2)廣度優先遍歷(BFS)

   廣度優先遍歷又稱爲廣度優先搜索(Breadth First Search),它相似於樹的層序遍歷,具體定義以下:假設從圖中某頂點v 出發,在訪問了v 以後依次訪問v 的各個不曾訪問過的鄰接點,而後分別從這些鄰接點出發依次訪問它們的鄰接點,並使「先被訪問的頂點的鄰接點」先於「後被訪問的頂點的鄰接點」被訪問,直至圖中全部已被訪問的頂點的鄰接點都被訪問到。若此時圖中尚有頂點未被訪問,則另選圖中一個不曾被訪問的頂點做起始點,重複上述過程,直至圖中全部頂點都被訪問到爲止。
  廣度優先遍歷也是一個遞歸的過程,咱們使用一個隊列(Queue)來實現圖的廣度優先遍歷。
  接下來,咱們將如下面的無向圖展現廣度優先遍歷:

   所以,使用一個隊列(Queue)輔助實現廣度優先遍歷(BFS)代碼以下(python 2.7):

 1 #建立一個圖
 2 Graph = {}
 3 Graph['A'] = ['B', 'C', 'D']
 4 Graph['B'] = ['A', 'E']
 5 Graph['C'] = ['A', 'F']
 6 Graph['D'] = ['A', 'G', 'H']
 7 Graph['E'] = ['B', 'F']
 8 Graph['F'] = ['E', 'C']
 9 Graph['G'] = ['D', 'H', 'I']
10 Graph['H'] = ['G', 'D']
11 Graph['I'] = ['G']
12 
13 
14 #使用隊列來實現廣度優先遍歷
15 def BFSTraverse(G, start):
16     from collections import deque
17     queue = deque()                      #初始化一個隊列
18     visited = set()                      #初始化一個存儲訪問過元素的集合
19     queue.append(start)                  #將起始結點加入隊列
20     while queue:                        #當隊列不爲空時,進入循環
21         node = queue.popleft()           #將隊列的隊首元素出隊
22         if node in visited:              #判斷該元素是否被訪問過,若是訪問過,跳出本次循環
23             continue
24         else:
25             print node,
26             visited.add(node)
27             for adj in G[node]:
28                 if adj not in visited:
29                     queue.append(adj)
30     print "\n"
31 
32 
33 
34 
35 if __name__ == '__main__':
36 
37     print "廣度優先搜索結果:"
38     BFSTraverse(Graph, 'A')

運行結果以下:

      廣度優先搜索結果:
      A B C D E F G H I

 

(3)DFS和BFS算法效率比較

  空間複雜度:二者的空間複雜度都是O(n),分別借用了堆棧和隊列。

  時間複雜度:對於二者而言,時間複雜度只與圖的存儲結構(鄰接矩陣或鄰接表)有關,而與搜索路徑無關。鄰接矩陣:O(n^2),鄰接表:O(n + e)。

 

四、最小生成樹

 4.1 什麼是生成樹

  在圖論的數學領域中,若是連通圖 G的一個子圖是一棵包含G 的全部頂點的樹,則該子圖稱爲G的生成樹(SpanningTree)。生成樹是連通圖的包含圖中的全部頂點的極小連通子圖。圖的生成樹不唯一。從不一樣的頂點出發進行遍歷,能夠獲得不一樣的生成樹。

  經常使用的生成樹算法有DFS生成樹、BFS生成樹、Prim 最小生成樹和Kruskal最小生成樹算法。
 
4.2 什麼是最小生成樹

  對於連通的帶權圖(連通網)G,其生成樹也是帶權的。生成樹T各邊的權值總和稱爲該樹的權,記做:

  其中,TE表示T的邊集,w(u,v)表示邊(u,v)的權。權最小的生成樹稱爲G的最小生成樹(Minimum SpannirngTree)。最小生成樹可簡記爲MST。

  求一個連通圖的最小生成樹的方法包括:Kruskal算法和Prim 算法。

4.3 最小生成樹算法

  接下來咱們將以一個無向圖來展現Kruskal算法和Prim 算法,無向圖以下所示:

(1)Kruskal算法

   Kruskal算法是一種用來查找最小生成樹的算法,它是基於貪心的思想獲得的。首先咱們把全部的邊按照權值先從小到大排列,接着按照順序選取每條邊,若是這條邊的兩個端點不屬於同一集合,那麼就將它們合併,直到全部的點都屬於同一個集合爲止。至於怎麼合併到一個集合,那麼這裏咱們就能夠用到一個工具——-並查集。換而言之,Kruskal算法就是基於並查集的貪心算法。

  Kruskal算法每次要從都要從剩餘的邊中選取一個最小的邊。一般咱們要先對邊按權值從小到大排序,這一步的時間複雜度爲爲O(|Elog|E|)。Kruskal算法的實現一般使用並查集,來快速判斷兩個頂點是否屬於同一個集合。最壞的狀況可能要枚舉完全部的邊,此時要循環|E|次,因此這一步的時間複雜度爲O(|E|α(V)),其中α爲Ackermann函數,其增加很是慢,咱們能夠視爲常數。因此Kruskal算法的時間複雜度爲O(|Elog|E|),其中E和V分別是圖的邊集和點集。

  所以,使用Kruskal算法查找最小生成樹的代碼以下(python 2.7):

 1 Graph = {'A': {'B': 6, 'E': 10, 'F': 12},
 2          'B': {'A': 6, 'C': 3, 'D': 5, 'F': 8},
 3          'C': {'B': 3, 'D': 7},
 4          'D': {'B': 5, 'C': 7, 'E': 9, 'F': 11},
 5          'E': {'A': 10, 'D': 9, 'F': 16},
 6          'F': {'A': 12, 'B': 8, 'D': 11, 'E': 16},
 7 }
 8 
 9 def Kruskal(G):
10     def f1(x):
11         return x[2]
12     record_node = set()                                                    #記錄添加的邊節點
13     mintree = []                                                           #最小生成樹的全部的邊
14     cost = []
15     edges = []                                                              #得到圖的全部的邊,並對它排序
16     for key1 in G.keys():
17         for key2 in G[key1].keys():
18             edges.append([key1, key2, G[key1][key2]])
19     edges.sort(key=f1, reverse=True)                                        #對全部的邊的成本進行從大到小排序
20 
21     while edges:
22         edge = edges.pop()                                                  #選取最短的邊
23         if (edge[0] in record_node) and (edge[1] in record_node):           #若是這兩個頂點同時在集合中,表示會造成環路,不能加入這條邊
24             continue
25         else:
26             record_node.add(edge[0])
27             record_node.add(edge[1])
28             cost.append(edge.pop())
29             mintree.append(edge)
30     print "克魯斯卡爾Kruskal算法最小生成樹:"
31     print "各個邊的權值: ", cost
32     print "最小生成樹的成本: ", sum(cost)
33     print "最小生成樹的邊: ", mintree
34     return cost, mintree
35 
36 if __name__ == '__main__':
37     cost,mintree = Kruskal(Graph)
38     print "\n"

運行結果以下:

      克魯斯卡爾Kruskal算法最小生成樹:
      各個邊的權值: [3, 5, 6, 8, 9]
      最小生成樹的成本: 31
      最小生成樹的邊: [['B', 'C'], ['D', 'B'], ['B', 'A'], ['F', 'B'], ['D', 'E']]

 

 (2)Prim算法

   普里姆算法(Prim算法)是圖論中的一種算法,可在加權連通圖裏搜索最小生成樹。意即由此算法搜索到的邊子集所構成的樹中,不但包括了連通圖裏的全部頂點,且其全部邊的權值之和亦爲最小。

  對於任意圖,假設包含n個頂點,m條邊。Prim算法是從頂點出發的,其算法時間複雜度與頂點數目有關係。(注意:prim算法適合稠密圖,其時間複雜度爲O(n^2),其時間複雜度與邊得數目無關,而Kruskal算法的時間複雜度爲O(ElogE)跟邊的數目有關,適合稀疏圖。)

 

  Prim算法是一種構造性算法。假設G=(V,E)是一個具備n個頂點的帶權連通無向圖,T=(U,TE)是G的最小生成樹,其中U是T的頂點集,TE是T的邊集,則由G構造從起始頂點v出發的最小生成樹T的步驟以下:

  (a)初始化U={v},以v到其餘頂點的全部邊爲候選邊;
  (b)重複如下步驟(n-1)次,使得其餘(n-1)個頂點被加入到U中:

    (1)從侯選邊中挑選權值最小的邊加入TE,設該邊在V-U中的頂點是k,將k加入U中;(加入後不能造成環)

    (2)考察當前V-U中全部頂點j,修改侯選邊,若邊(k,j)的權值小於原來和頂點j關聯的侯選邊,則用邊(k,j)取代後者做爲侯選邊。(加入後不能造成環)

   所以,使用Prim算法查找最小生成樹的代碼以下(python 2.7):

 1 Graph = {'A': {'B': 6, 'E': 10, 'F': 12},
 2          'B': {'A': 6, 'C': 3, 'D': 5, 'F': 8},
 3          'C': {'B': 3, 'D': 7},
 4          'D': {'B': 5, 'C': 7, 'E': 9, 'F': 11},
 5          'E': {'A': 10, 'D': 9, 'F': 16},
 6          'F': {'A': 12, 'B': 8, 'D': 11, 'E': 16},
 7 }
 8 
 9 def Prim(G):
10     U = set(G.keys())                            #圖G的頂點集合U,它包含了該圖的全部頂點
11     V = set(G.keys()[0])                         #將起始頂點加入集合V
12     min_tree = []                                #存儲要返回的最小生成樹的全部的邊
13     cost = []                                    #記錄最小生成樹各邊的權重的值
14     while U.difference(V):                       #當集合U和V不想等時,進入循環
15         min_value = float("inf")                 #初始化一個最小值
16         node1 = None                             #用於記錄加入邊的第一個節點
17         node2 = None                             #用於記錄加入邊的第二個節點
18         for v in V:                              #遍歷訪問過的節點
19             for u in U.difference(V):            #遍歷未訪問過的節點
20                 if u in G[v]:                    #若是兩個節點之間存在相連的邊
21                     if G[v][u] < min_value:      #判斷該值是不是全部訪問過節點存在的相鄰邊中的最小值
22                         min_value = G[v][u]       #更新邊權重的最小值
23                         node1 = v                 #記錄邊權重最小值的第一個節點
24                         node2 = u                 #記錄邊權重最小值的第二個節點
25         V.add(node2)                              #將第二個節點加入到訪問過的節點集合V,由於第二個節點來自於未訪問過的節點集合
26         min_tree.append([node1, node2])           #將包括兩個節點的邊加入到最小生成樹邊列表
27         cost.append(min_value)                    #將邊的權重加入到成本列表中
28     print "普利姆Prim算法最小生成樹:"
29     print "各個邊的權值: ", cost
30     print "最小生成樹的成本: ", sum(cost)
31     print "最小生成樹的邊: ", min_tree
32     return cost, min_tree
33 
34 if __name__ == '__main__':
35    
36     Prim(Graph)

運行結果以下:

      普利姆Prim算法最小生成樹:
      各個邊的權值: [6, 3, 5, 8, 9]
      最小生成樹的成本: 31
      最小生成樹的邊: [['A', 'B'], ['B', 'C'], ['B', 'D'], ['B', 'F'], ['D', 'E']]

 

(3)兩種算法比較

 

五、最短路徑問題

相關文章
相關標籤/搜索