数据结构之图:加权无向图与寻找最小生成树,Python——27
加權無向圖與prim算法和Kruskal算法尋找最小生成樹
加權無向圖的介紹
引入
- 加權無向圖是一種為每條邊關聯一 個權重值或 是成本的圖模型。這種圖能夠自然地表示許多應用。在一副航空圖中,邊表示航線,權值則可以表示距離或是費用。在一副電路圖中,邊表示導線,權值則可能表示導線的長度即成本,或是信號通過這條先所需的時間。此時我們很容易就能想到,最小成本的問題,例如,從西安飛紐約,怎樣飛才能使時間成本最低或者是金錢成本最低?
- 給圖的邊賦予一定的權重標準,構成加權無向圖
加權無向圖中的邊我們就不能簡單向之前一樣使用v-w兩個頂點表示了(之前的adj_list列表中存放的是頂點,而必須要給邊關聯一個權重值,因此我們可以使用對象來描述一條邊(現在要存放邊對象Edge)。
構造加權無向邊
屬性及方法設計
vertex1和vertex2分別表示要構造的邊的兩端的頂點;weight表示要構造的邊的權重
加權無向邊Python代碼實現
class Edge:def __init__(self, vertex1, vertex2, weight):self.vertex1 = vertex1self.vertex2 = vertex2self.weight = weightdef get_weight(self):return self.weightdef either(self):"""Return either vertex of this Edge"""return self.vertex1def opposite(self, v):return self.vertex1 if v == self.vertex2 else self.vertex2def compare(self, edge):return 1 if self.weight > edge.weight else (-1 if self.weight < edge.weight else 0)代碼較簡單,測試就略過了,這里實現的Edge對象將用于后面的類方法實現。
Prim算法跳轉
Kruskal算法跳轉
Python代碼實現加權無向圖
from Structure.graph.Edge import Edgeclass WeightedUndigraph:def __init__(self, v):self.num_vertices = vself.num_edges = 0self.adj_list = [[] for _ in range(v)]def get_num_vertices(self):return self.num_verticesdef get_num_edges(self):return self.num_edgesdef add_edge(self, edge: Edge):v1 = edge.either()v2 = edge.opposite(v1)self.adj_list[v1].append(edge)self.adj_list[v2].append(edge)self.num_edges += 1def adjacent_edges(self, vertex):return self.adj_list[vertex]def all_edges(self):all_edges = []for i in range(self.num_vertices):for edge in self.adj_list[i]:if edge.opposite(i) < i: all_edges.append(edge)return all_edges最小生成樹的介紹
引入
舉例
- 省領導審查全省各市,如何找出最優最快路徑完成審查
- 所有連接這些城市的道路中,路徑(權重)最短的則是最優路徑,此時要用到最小生成樹
最小生成樹的定義
- 最小生成樹(Minimum spanning tree)或說最小權重生成樹(Minimum weight spanning tree),是一幅連通的加權無向圖中所有的邊集合的一個子集,并且這些邊必須連接所有頂點,這個子集它必須是無環的、權重和是最小的
- 對于連通圖,一個圖中可能有多顆生成樹,最小生成樹其實是最小權重生成樹的簡稱。
- 廣義上而言,對于非連通無向圖來說,它的每一連通分量同樣有最小生成樹,它們的并被稱為最小生成森林。
約定
因為非連通圖,無法找出連接全部頂點的邊
最小生成樹的性質
- 用一條邊連接最小生成樹中的任意兩個(不相鄰的)頂點都會產生一 個新的環;
- 刪除最小生成樹的任意一條邊,將會得到兩棵獨立的樹;
尋找最小生成樹(Minimum Spanning Tree)
在實現尋找最小生成樹之前,我們先來了解一下實現尋找最小生成樹的兩個原理
切分定理
切分定理
先了解概念
要從一副連通圖中找出該圖的最小生成樹,需要通過切分定理完成。
- 切分:將圖的所有頂點按照某些規則分為兩個非空且沒有交集的集合。
- 橫切邊:連接兩個屬于不同集合的頂點的邊稱之為橫切邊。
例如我們將圖中的頂點切分為兩個集合,色頂點于一個集合,色頂點屬于外一個集合,那么效果如下:
再了解定義
切分定理:
- 在一副加權圖中, 給定任意的切分,它的橫切邊中的權重最小者必然屬于圖中的最小生成樹。
- 注意:一次切分產生的多個橫切邊中,權重最小的邊不一定是所有橫切邊中唯一屬于圖的最小生成樹的邊。也有可能非最小權重的橫切邊也是最小生成樹的邊
貪心算法(Greedy Algorithm)
定義
- 任何在想要尋找全局最優方法時,在所有單個階段進行探索時都嘗試尋找當前階段的局部最優解,以嘗試獲取最終的全局最優解,這樣的算法都可以稱之為貪心算法。
- 在許多問題中,貪心算法的思想并不能總是獲得一個最優解,但是貪心算法的探索過程,會生成許多的局部最優解,這些局部最優解在合理的時間內,也可以近似地等于一個全局最優解
Prim算法
計算圖的最小生成樹的算法有很多種,但這些算法都可以看做是貪心算法的一種特殊情況,這些算法的不同之處在于保存切分和判定權重最小的橫切邊的方式。
prim算法定義
- prim算法是一種用于尋找加權無向圖中最小生成樹的貪心算法,prim算法選擇隨機的一個頂點開始,把這個頂點當做處于MST的集合,而把其他不在這顆MST中的頂點標記為不處于MST中的集合,然后通過不斷的重復做某些操作,可以逐漸將非最小生成樹中的頂點加入到最小生成樹中,直到所有的頂點都加入到最小生成樹中。
Prim算法的切分規則:
- 把最小生成樹中的頂點看做是一個集合 ,把不在最小生成樹中的頂點看做是另外一個集合。
- 最開始是把任意一個結點當做最小生成樹的結點;而剩下的其他結點則不是樹中的結點
在實現prim算法尋找MST之前,我們要借助一個最小索引優先隊列,來方便地取出最小權重邊:
- 引入索引最小優先隊列the_cut_edges,它的索引代表圖的頂點,儲存的值代表從其他的某個頂點到當前索引對應頂點的邊的權重,當遍歷完某個頂點的鄰接邊時,the_cut_edges就會儲存所有該頂點通向其他頂點的邊的權重(重復的權重后面會有設計方法來避免),當最后遍歷完時,我們只需要調用索引最小優先隊列的delete_min_and_get_index()即可獲取最小權重對應的頂點
索引最小有限隊列代碼傳送門
加權無向邊,up↑
加權無向圖,up↑
Prim算法Python代碼實現尋找MST
from Structure.graph.WeightedUndigraph import WeightedUndigraph from Structure.graph.Edge import Edge from Structure.PriorityQueue.IndexMinpriorutyQueue import IndexMinPriorityQueue from math import infclass PrimMST:def __init__(self, graph):"""MST here represent the Minimum Spanning Tree of the current loop"""self.graph = graph# Memorize the cheapest edge to MST of each vertex(index)self.min_edge_to_MST = [None for _ in range(self.graph.get_num_vertices())]# Store the smallest weight of each vertex(index)'s edge to MST;# Initialize it with infinite plus, we will compare out a minimum weight afterself.min_weight_to_MST = [+inf for _ in range(self.graph.get_num_vertices())]# Mark a True if a vertex(index) has been visitedself.marked = [False for _ in range(self.graph.get_num_vertices())]# Memorize the smaller weight of each vertex(index)'s edge connected to MSTself.the_cut_edges = IndexMinPriorityQueue(self.graph.get_num_vertices())# Initialize a 0.0 as the minimum weight to weight_to_MSTself.min_weight_to_MST[0] = 0.0self.the_cut_edges.insert(0, 0.0)while not self.the_cut_edges.is_empty():# Take out the minimum-weighted vertex, and make a visit(update) for itself.visit(self.the_cut_edges.delete_min_and_get_index())def visit(self, v):"""Update the MST"""self.marked[v] = Truefor e in self.graph.adjacent_edges(v):w = e.opposite(v)# Check if the opposite vertex of v in edge e is marked, if did, skip this loopif self.marked[w]:continue# Find out the minimum-weighted-edge vertex opposite to this vertex(v)if e.get_weight() < self.min_weight_to_MST[w]: # e.get_weight():Get weight of the edge between v and w# Update the minimum edge and weight# print(f"v: {v}, w: {w}, min_weight_edge: {e.get_weight()}")self.min_edge_to_MST[w] = eself.min_weight_to_MST[w] = e.get_weight()if self.the_cut_edges.is_index_exist(w):# print(w, e.get_weight())self.the_cut_edges.change_item(w, e.get_weight())else:self.the_cut_edges.insert(w, e.get_weight())def min_weight_edges(self):return [edge for edge in self.min_edge_to_MST if edge]if __name__ == '__main__':with open('../MST.txt', 'r') as f:num_vertices = int(f.readline())num_edges = int(f.readline())graph = WeightedUndigraph(num_vertices)for e in range(num_edges):v1, v2, w = f.readline().split()graph.add_edge(Edge(int(v1), int(v2), float(w)))P_MST = PrimMST(graph)for e in P_MST.min_weight_edges():v = e.either()w = e.opposite(v)weight = e.weightprint(f"v: {v} w: {w} weight: {weight}")運行結果
v: 1 w: 7 weight: 0.19 v: 0 w: 2 weight: 0.26 v: 2 w: 3 weight: 0.17 v: 4 w: 5 weight: 0.35 v: 5 w: 7 weight: 0.28 v: 6 w: 2 weight: 0.4 v: 0 w: 7 weight: 0.16即是最小生成樹的所有邊的權重和
MST.txt
8 16 4 5 0.35 4 7 0.37 5 7 0.28 0 7 0.16 1 5 0.32 0 4 0.38 2 3 0.17 1 7 0.19 0 2 0.26 1 2 0.36 1 3 0.29 2 7 0.34 6 2 0.40 3 6 0.52 6 0 0.58 6 4 0.93Kruskal算法
定義
- kruskal算法圖論中尋找加權無向圖的最小生成樹的另一種貪心算法,它的主要思想是按照邊的權重(從小到大)處理它們,將邊加入最小生成樹中,加入的邊不會與已經加入最小生成樹的邊構成環,直到樹中含有N-1條邊為止。
kruskal算法和prim算法的區別:
- Prim算法是一次一條邊的構造最小生成樹,每一步都為一棵樹添加一條邊。kruskal算法構造最小生成樹的時候也是一次一條邊地構造,但它的切分規則是不一樣的。它每一次尋找的邊會連接一片森林中的兩棵樹。如果一副加權無向圖由V個頂點組成 ,初始化情況下每個頂點都構成一棵獨立的樹,則V個頂點對應V棵樹,組成一片森林, kruskal算法每一次處理都會將兩棵樹合并為一棵樹,直到整個森林中只剩一棵樹為止。
實現步驟:
Kruskal算法在尋找MST時,需要用到并查集來實現,當尋找的樹不是連通的時候(初始時所有結點都是分隔開的),它就會為在每一顆樹中搜尋出一個MST。
主要屬性和方法設計
UFT是一個優化后的并查集UF_Tree_Weighted對象,索引代表頂點,使用其in_the_same_group(v, w)可以判斷兩個頂點是否在同一顆樹上,使用其unite()方法可以將兩個頂點所在的樹合并
all_edges 是一個最小優先隊列MinPriorityQueue的對象,儲存圖中所有的邊,并使用最小優先隊列進行按照權重對邊進行排序
edges_MST 是一個列表(當做隊列),儲存了MST的所有邊,get_all_edges_mst()返回的就是它,可以直觀地觀察MST的邊
并查集傳送、
最小優先隊列傳送(最小優先隊列需要小小地修改比較的方法,應該比較傳入的Edge對象的weight,否則直接比較會報錯):
優化后的并查集傳送
最小優先隊列傳送
加權無向邊
加權無向圖
Kruskal算法Python代碼尋找MST
from Structure.UF.UF_Tree_Weighted import UF_Tree_Weighted from Structure.PriorityQueue.MinPriorityQueue import MinPriorityQueue from Structure.graph.WeightedUndigraph import WeightedUndigraph from Structure.graph.Edge import Edgeclass KruskalMST:def __init__(self, graph):self.graph = graphself.N = self.graph.num_verticesself.UFT = UF_Tree_Weighted(self.N)self.all_edges = MinPriorityQueue()self.edges_MST = []for e in self.graph.get_all_edges():self.all_edges.append(e)while not self.all_edges.is_empty() and len(self.edges_MST) < self.N - 1:min_edge = self.all_edges.extract_min()v = min_edge.either()w = min_edge.opposite(v)if self.UFT.in_the_same_group(v, w):continueself.UFT.unite(v, w)self.edges_MST.append(min_edge)def get_all_edges_mst(self):return self.edges_MSTif __name__ == '__main__':with open('../MST.txt', 'r') as f:num_vertices = int(f.readline())num_edges = int(f.readline())graph = WeightedUndigraph(num_vertices)for e in range(num_edges):v1, v2, w = f.readline().split()graph.add_edge(Edge(int(v1), int(v2), float(w)))K_MST = KruskalMST(graph)for e in K_MST.get_all_edges_mst():v = e.either()w = e.opposite(v)weight = e.weightprint(f"v: {v} w: {w} weight: {weight}")運行結果:
v: 0 w: 7 weight: 0.16 v: 2 w: 3 weight: 0.17 v: 1 w: 7 weight: 0.19 v: 0 w: 2 weight: 0.26 v: 5 w: 7 weight: 0.28 v: 4 w: 5 weight: 0.35 v: 6 w: 2 weight: 0.4MST.txt
8 16 4 5 0.35 4 7 0.37 5 7 0.28 0 7 0.16 1 5 0.32 0 4 0.38 2 3 0.17 1 7 0.19 0 2 0.26 1 2 0.36 1 3 0.29 2 7 0.34 6 2 0.40 3 6 0.52 6 0 0.58 6 4 0.93總結
以上是生活随笔為你收集整理的数据结构之图:加权无向图与寻找最小生成树,Python——27的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php 动态参数,php怎么实现动态传参
- 下一篇: 智慧交通day02-车流量检测实现12: