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


  

  

數據結構——圖

1、圖的基本概念

2、圖的數據表示法

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。有向圖則不一定如此。

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

  (3)用鄰接矩陣法表示圖共需要 n^2 個單位空間,由於無向圖的鄰接矩陣具有對稱關系的,扣除對角線全部為 0 外,僅需要存儲上三角形數據即可,因此僅需要n(n-1)/2。

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

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

  我們接下來使用程序來創建鄰接矩陣表示這個無向圖,該程序使用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 }

 

3、圖的遍歷

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

  由於圖結構本身的復雜性,所以圖的遍歷操作也較復雜,主要表現在以下四個方面:

  (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、最小生成樹

 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)兩種算法比較

 

5、最短路徑問題

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM