圖是一種靈活的資料結構,它多用於描述對象之間的關係和串連模型。
關於圖的演算法:最小產生樹、最短路徑、旅行商問題以及許多其他演算法大都會使用到廣度優先搜尋和深度優先搜尋,因為它們提供了一套系統地訪問圖資料結構的方法。
帶權圖,是指圖的每條邊上帶有一個值或權,這些權用一個小的數字標記在邊上。很多條件因素都可以作為權值,但通常它表示遍曆這條邊所產生的代價。
最小產生樹簡述
我們做一個簡單的模型,在一塊木板上釘上一些釘子並用一些細繩串連起來,假設每一個釘子都經由一根或多根細繩串連起來。現在我們一根一根拿走細繩,直到用最少的細繩將所有釘子串連起來。這個模型的背後思想就是最小產生樹。
正式的表述法是,給定一個無方向的帶權圖G=(V,E),最小產生樹為集合T,T是以最小代價串連V中所有頂點所用邊E的最小集合。集合T中的邊能形成一顆樹,這是因為每個頂點都能向上找到它的一個父節點(根結點除外)。
Prim演算法
Prim演算法是一種產生最小產生樹的方法。
Prim演算法從任意一個頂點開始,每次選擇一個與當前頂點最近的一個頂點,並將兩個頂點之間的邊加入到樹中。
從根本上講,Prim演算法就是不斷的選擇頂點,並計算邊的權值,同時判斷是否還有更有效串連方式。該演算法類似廣度優先搜尋演算法,因為在往圖中更深的頂點探索之前,它首先要遍曆與此頂點相關的所有頂點。每個階段都要決定選擇哪個頂點,所以需要維護頂點的顏色和索引值。
開始,將所有頂點的色值設定為白色,索引值設定為∞(它代表一個足夠大的數,大於圖中所有邊的權值)。同時,將起始頂點的索引值設定為0。隨著演算法的不斷演化,在最小產生樹中為每個頂點(除起始頂點外)指派一個父節點。只有當頂點的色值變為黑色時,此頂點才是最小產生樹的一部分。
Prim演算法的運行過程如下:
首先,在圖中所有的白色頂點中,選擇索引值最小的頂點u。開始,索引值被設定為0的那一頂點將作為起始頂點。
當選擇此頂點之後,將其標記為黑色。
接下來,對於每個與u相鄰的頂點v,設定v的索引值為邊(u,v)的權值,同時將u設定為v的父結點。
重複這個過程,直到所有的頂點都標記為黑色。
隨著最小產生樹的增長,該樹會包含圖中的所有的邊(串連所有頂點最少數量邊),且每條邊的兩端都有一個黑色的頂點。
展示了最小產生樹的產生過程。在圖中,索引值和父結點都顯示在每個頂點的旁邊,用斜線分開。索引值顯示在斜線的左邊,父結點顯示在斜線的右邊。淺灰色的邊是最小產生樹增長過程中的邊。圖中最小產生樹總的權值為17。
最小產生樹的介面定義
mst
int mst(Graph *graph, const MstVertex *start, List *span, int (*match)(const void *key1, const void *key2));
傳回值:如果計算最小產生樹成功,返回0,否則返回-1。
描述:為一個無方向的帶權圖graph計算最小產生樹。
最小產生樹從頂點start開始。
此操作會改變graph,所以如果有必要,在調用此操作之前先對圖進行備份。
graph中的每個頂點必須包含MstVertex類型的資料。通過設定MstVertex結構體中的成員weight的值來指定每個邊的權值,weight的值由傳入graph_ins_edge的參數data2決定。用MstVertex結構體的成員data來儲存與頂點相關的資料。
graph的match函數(此函數在用graph_init對圖進行初始化時調用)用來比較MstVertex結構體中的data成員。此函數與傳入mst中的參數match相同。
一旦計算完成,最小產生樹的相關資料將會返回到span。span是儲存MstVertex結構體的列表。在span中,父結點為NULL的頂點為最小產生樹的根結點。其他每個頂點的parent成員都指向span中位於該頂點之前的那個頂點。
span中的頂點指向graph中的實際頂點,所以只要能夠訪問span,函數調用者就必須保證graph中記憶體空間有效。一旦不再使用span,就調用list_destroy銷毀span。
複雜度:O(E V2),其中V是圖中頂點的個數,E是邊的條數。
最小產生樹的實現與分析
為了計算一個無方向的帶權圖的最小產生樹,首先,我們要用表示圖的基本抽象資料類型來表示帶權圖。同時,Prim演算法還需要一種追蹤頂點和邊資訊的方法。這就用到了MstVertex結構體:它用來為圖中的頂點計算最小產生樹。此結構包含5個成員:data是與頂點相關的資料;weight是到達該頂點的邊的權值;color是頂點的顏色;key是頂點的索引值;parent是最小產生樹中頂點的父結點。
建立一個包含MstVertex結構體的圖的過程幾乎與建立一個包含其他類型的圖的過程一樣:要將一個頂點插入圖中,調用graph_ins_vertex,並將MstVertex結構體傳入data。類似地,要將一條邊插入圖中,調用函數graph_ins_edge,並將MstVertex結構體傳入data1和data2。當插入一個頂點時,只設定MstVertex結構體的data成員。當插入一條邊時,設定data1的data成員,data2的data和weight成員。在data2中,weight是邊的權值,此邊是data1中的頂點到data2中頂點的連接線。在實際中,權值通常用浮點數進行儲存和計算。由於索引值是由權值計算來的,因此索引值也用浮點數表示。
mst操作首先初始化鄰接表結構鏈表中的每個頂點。將每個頂點的索引值初始化為DBL_MAX(除超始頂點外,超始頂點初始值為0.0)。在圖的抽象資料類型中,圖由一個鄰接表結構鏈表來表示。每個鄰接表包含一個頂點和一個相鄰頂點的集合。用儲存在鄰接表結構中的頂點來維護頂點的色值、索引值、和父結點。維護鄰接表結構鏈表中資訊的關鍵是能將這些資訊儲存起來,而不是僅僅列出與自己相鄰的頂點。鑒於一個頂點可能會出現在眾多的鄰接表中,所以每個頂點只能在鄰接表結構鏈表中出現一次。
Prim演算法的核心是用一個單迴圈為圖中的每個結點迭代一次。在每次迭代的過程中:
首先,在所有的白色頂點中選擇索引值最小的頂點。同時,在鄰接表結構鏈表把此頂點塗黑。
接下來,遍曆與所選頂點相鄰的頂點。在遍曆每個頂點時,檢查它在鄰接表結構鏈表中的顏色和索引值。一旦擷取了這些資訊,就將它與所選頂點的顏色和索引值進行比較。如果相鄰頂點是白色,且其索引值比所選頂點的小,就將所選頂點與相鄰頂點之間邊的權值設定為相鄰頂點的索引值;同時,將相鄰頂點的父結點設定為所選頂點。
然後,更新儲存在鄰接表結構鏈表中相鄰頂點的資訊。
重複這個過程,直到所有頂點都塗黑。
一旦Prim演算法中的主迴圈結束,最小產生樹也就計算完成了。此時,將鄰接表結構鏈表中的每個黑色MstVertex結構體插入到鏈表span中。在span中,父結點為NULL的頂點就是最小產生樹的根結點。其他每個頂點的parent成員都指向span中位於該頂點之前的那個頂點。每個MstVertex結構體的成員weight並不經常使用,因為它只有在儲存到鄰接表中時才用的到。
顯示了上面的樣本圖中計算最小產生樹所返回的MstVertex結構體鏈表:
樣本:圖演算法的標頭檔(含最小產生樹、最短路徑、旅行商問題三種實現所需函數定義的標頭檔)
/*graphalg.h*/#ifndef GRAPHALG_H#define GRAPHALG_H#include "graph.h"#include "list.h"/*定義最小產生樹中結點的資料結構*/typedef struct MstVertex_{ void *data; double weight; VertexColor color; double kdy; struct MstVertex_ *parent;}MstVertex; /*定義最短路徑中結點的資料結構*/typedef struct PathVertex_{ void *data; double weight; VertexColor color; double d; struct PathVertex_ *parent;}PathVertex;/*定義旅行商問題中結點的資料結構*/typedef struct TspVertex_{ void *data; double x,y; VertexColor color;}TspVertex;/*函數介面*/int mst(Graph *graph, const MstVertex *start, List *span, int (*match)(const void *key1, const void *key2));int shortest(Graph *graph, const PathVertex *start, List *paths, int(*match)(const void *key1, const void *key2));int tsp(List *vertexs, const TspVertex *start, List *tour, int (match*)(const void *key1, const void *key2));#endif // GRAPHALG_H
樣本:計算最小產生樹的實現
/*mst.c*/#include <float.h>#include <stdlib.h>#include "graph.h"#include "list.h"/*mst 計算最小產生樹函數*/int mst(Graph *graph, const MstVertex *start, List *span, int (*match)(const void *key1, const void *key2)){ AdjList *adjlist; MstVertex *mst_vertex, *adj_vertex; ListElmt *element, *member; double minmum; int found,i; /*初始化圖中的所有結點*/ found=0; for(element=list_head(&graph_adjlists(graph)); element!=NULL; element = list_next(element)) { mst_vertex = ((AdjList *)list_data(element))->vertex; if(match(mst_vertex,start)) { /*匹配到起始頂點,並對其初始化*/ mst_vertex->color = white; mst_vertex->key = 0; mst_vertex->parent = NULL; found = 1; } else { /*非起始頂點的初始化*/ mst_vertex->color = white; mst_vertex->key = DBL_MAX; mst_vertex->parent = NULL; } } /*未找到起始頂點,函數返回*/ if(!found ) return -1; /*運用Prim演算法計算最小產生樹*/ i=0; while(i<graph_vcount(graph)) { /*選擇擁有最小索引值的白色頂點*/ minimum = DBL_MAX; for(element = list_head(&graph_adjlists(graph)); element != NULL; element = list_next(element)) { mst_vertex = ((AdjList *)list_data(element))->vertex; if(mst_vertex->color == white && mst_vertex->key < minmum) { minmum = mst_vertex->key; adjlist = list_data(element); } } /*將已選擇的頂點塗成黑色*/ ((MstVertex *)adjlist->vertex)->color = black; /*遍曆被選中頂點的所有鄰接頂點*/ for(member = list_head(&adjlist->adjacent); member != NULL; member = list_next(member)) { adj_vertex = list_data(member); for(element = list_head(&graph_adjlists(graph)); element != NULL; elemet = list_next(element)) { mst_vertex = ((AdjList *)list_data(element))->vertex; if(match(mst_vertex,adj_vertex)) { /*決定是否改變該頂點的索引值或父結點*/ if(mst_vertex->color == white && adj_vertex->weight < mst_vertex->key) { mst_vertex->key = adj_vertex->weight; mst_vertex->parent = adjlist_vertex; } break; } } } /*準備選取下一個頂點*/ i++; }/*載入最小產生樹到鏈表中*/list_init(span,NULL);/*從鄰接表結構鏈表中載入每個黑色結點*/for(element = list_head(&graph_adjlists(graph)); elemet != NULL; element = list_next(element)){ /**/ mst_vertex = ((AdjList *)list_data(element))->vertex; if(mst_vertex->color == black) { if(list_ins_next(span, list_tail(span),mst_vertex) != 0) { list_destroy(span); return -1; } }} return 0;}