在解決網絡路由的問題中,尋找圖中一個頂點到另外一個頂點的最短路徑或最小帶權路徑是很是重要的過程。算法
正式表述爲,給定一個有向帶權圖G=(V,E),頂點s到V中頂點t的最短路徑爲在E中邊的集合S中鏈接s到t代價最小的路徑。網絡
當找到S時,咱們就解決了單對頂點最短路徑問題。要作到這一點,實際上首先要解決更爲通常的單源最短路徑問題,單源最短路徑問題是解決單對頂點最短路徑過程當中的一部分。在單源最短路徑問題中,計算從一個頂點s到其餘與之相鄰頂點之間的最短路徑。之因此要用這個方法解決此問題是由於沒有其餘更好的辦法能用來解決單對頂點最短路徑問題。函數
解決單源最短路徑問題的方法之一就是Dijkstra算法。性能
Dijkstra算法會生成一棵最短路徑樹,樹的根爲起始頂點s,樹的分支爲從頂點s到圖G中全部其餘頂點的最短路徑。此算法要求圖中全部的權值均爲非負數。與Prim算法同樣,Dijkstra算法也是一種利用貪心算法計算並最終可以產生最優結果的算法。spa
從根本上說,Dijkstra算法經過選擇一個頂點,並不斷地探索與此頂點相關的邊,以此來肯定每一個頂點的最短路徑最否是最優。此算法相似廣度優先搜索算法,由於在往圖中更深的頂點探尋以前首先要遍歷與此頂點相關的全部頂點。爲了計算s與其餘全部頂點以前的最短路徑,Dijkstra算法須要維護每一個頂點的色值和最短路徑估計。一般,最短路徑估計由變量d表示。設計
開始,將全部色值設置爲白色,最短路徑估計設置爲∞(表明一個足夠大的數,大於圖中全部邊的權值)。將起始頂點的最短路徑估計設置爲0。隨着算法的不斷演進,在最短路徑樹中爲每一個頂點(除起始頂點)指派一個父結點。在算法結束以前,頂點的父結點可能會發生幾回變化。指針
Dijkstra算法的運行過程以下:code
首先,在圖中全部白色頂點之間,選擇最短路徑估計值最小的頂點u。初始,最短路徑估計值被設置爲0的頂點將作爲起始頂點。當選擇此頂點後,將其塗成黑色。blog
接下來,對於每一個與u相鄰的頂點v,釋放其邊(u,v)。當釋放邊後,咱們要確認是否要更新到目前爲止所計算的最短路徑。方法就是將(u,v)的權值加到u的最短路徑估計中。若是這個合計值小於或等於v的最短路徑估計,就將這個值指派給v,做爲v的新最短路徑估計。同時,將u設置爲v的父結點。接口
重複這個過程,直到全部的頂點都標記爲黑色。一旦計算完最短路徑樹,那麼從s到另一個頂點t的最短路徑就能惟一肯定:從樹中t處的結點開始向隨後的父結點查找,直到到達s。此尋找路徑的反向路徑即爲s到t的最短路徑。
下圖展現了由a到圖中其餘頂點的最短路徑的計算過程。例如,a到b的最短路徑爲(a,c,f,b),其權值爲7。最短路徑估計和每一個頂點的父結點都顯示在每一個頂點的旁邊。最短路徑估計顯示在斜線的左邊,父結點顯示在斜線的右邊。淺灰色的邊是最短路徑樹增加過程當中的邊。
shortest
int shortest(Graph *graph, const PathVertex *start, List *paths, int (*match)(const void *key1, const void *key2));
返回值:若是計算最短路徑成功,返回0;不然,返回-1。
描述:用於計算頂點start與有向帶權圖graph中其餘全部頂點之間的最短路徑。此操做會改變graph,因此若是有必要,在調用此操做以前先對圖進行備份。
graph中的每一個頂點必須包含PathVertex類型的數據。經過設置PathVertex結構體中的成員weight的值來指定每一個邊的權值,weitht的值由傳入graph_ins_edge的參數data2決定。用PathVertex結構體的成員data來保存與頂點相關的數據,例如頂點的標識符。
graph的match函數(此函數在用graph_init對圖進行初始化時調用)用來比較PathVertex結構體中的data成員。此函數與傳入shortest中的參數match相同。
一旦計算完成,最短路徑的相關信息將會返回給paths,paths是存儲PathVertex結構體的列表。在paths中,起始頂點的父結點設置爲NULL。而其餘每一個頂點的parents成員都指向位於該頂點以前的那個頂點,這個頂點位於從起始頂點開始的最短路徑之上。paths中的頂點指向graph中的實際頂點,因此只要可以訪問paths,函數調用都就必需要保證graph中的內存空間有效。一旦再也不使用paths,就調用list_destroy來銷燬paths。
複雜度:O(EV2),其中V是圖中的頂點個數,E是邊的條目數。
爲了計算有向帶權圖中一個頂點到其餘全部頂點的最短路徑,其圖的表示方法與最小生成樹中的表示方法相同。只是用PathVertex結構體取代頂點MstVertex結構。
PathVertex可以表示帶樹圖,同時可以追蹤Dijkstra算法所須要的頂點和邊的信息。此結構體包含5個成員:data是與頂點相關的數據;weight是到達該頂點的邊的權值;color是頂點的顏色;d是頂點的最短路徑估計;parent是最短路徑中頂點的交結點。
構造一個包含pathvertex結構體的圖的過程與構造一個包含MstVertex結構體的圖的過程相同。
shortest操做首先初始化鄰接表結構鏈表中的每一個頂點。將每一個頂點的最短路徑估計初始化爲DBL_MAX(起始頂點除外,起始頂點的初始值爲0.0)。用存儲在鄰接表結構鏈表中的頂點來維護頂點的色值、最短路徑估計和父結點。其緣由與計算最小生成樹時的解釋相同。
Dijkstra算法的核心是用一個單循環爲圖中的每一個結點迭代一次。在每次的迭代過程當中,首先在所選的白色頂點中選擇最短路徑估計最小的頂點。同時,在鄰接表結構鏈表中將此頂點塗黑。接下來,遍歷與所選頂點相鄰的頂點。在遍歷每一個頂點時,檢查它在鄰接表結構鏈表中的顏色和最短路徑估計。一旦得到了這些信息,就調用relax釋放所選頂點與相鄰頂點間的邊。若是此過程當中發現須要更新相鄰頂點的最短路徑估計和父結點,那麼就在鄰接表結構鏈表中更新此頂點。重複這個過程直到全部頂點都塗成黑色。
一旦Dijkstra算法中的主循環結束,計算圖中起始頂點到全部其餘頂點的最短路徑的過程也就完成了。此時,將鄰接表結構鏈表中每一個黑色的PathVertex結構體插入鏈表paths中。在paths中,父結點爲NULL的頂點就是起始頂點。其餘每一個頂點的parent成員都指向從起始頂點開始的最短路徑中的前一個頂點。每一個PathVertex結構體的成員weight並不常用,由於它只在存儲到鄰接表中時才用的到。下圖展現了在上圖1中計算最短路徑時所返回的PathVertex結構體列表。
/*shortest.c*/ #include <float.h> #include <stdlib.h> #include "graph.h" #include "graphalg.h" #include "list.h" #include "set.h" /*relax 釋放邊、更新最短路徑估計值、更新父結點*/ static void relax(PathVertex *u, PathVertex *v, double weight) { /*釋放頂點u和v之間的邊*/ if(v->d > u->d + weight) { v-> = u->d + weight; v->parent = u; } return; } /*shortest 最短路徑計算函數*/ int shortest(Graph *graph, const PathVertex *start, List *paths, int (*match)(const void *key1, const void key2)) { AdjList *adjlist; PathVertex *pth_vertex, *adj_vertex; ListElmt *element, member; double minimum; int found,i; /*初始化圖中的全部頂點*/ found = 0; for(element = list_head(&graph_adjlists(graph)); element != NULL; element = list_next(element)) { pth_Vertex = ((AdjList *)list_data(element))->vertex; if(match(pth_vertex, start)) { /*找到並初始化起始頂點*/ pth_vertex->color = white; pth_vertex->d = 0; pth_vertex->parent = NULL; found = 1; } else { /*非起始頂點,初始化*/ pth_vertex->color = white; pth_vertex->d = DBL_MAX; pth_vertex->parent = NULL; } } /*若是未匹配到起始頂點,程序退出*/ if(!found) return -1; /*使用Dijkstra算法計算從起始頂點開始的最短路徑*/ i=0; while(i < graph_vcount(graph)) { /*從全部的白色頂點中,選擇最短路徑估計值最小的頂點*/ minimum = DBL_MAX; for(element=list_head(&graph_adjlists(graph)); element!=NULL; element = list_next(element)) { pth_vertex = ((AdjList*)list_data(element))->vertex; if(pth_vertex->color == white && pth_vertex->d < minimum) { minimum = pth_vertex->d; adjlist = list_data(element); } } /*將該頂點塗成黑色*/ ((PathVertex *)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; element = list_next(element)) { pth_vertex = ((AdjList *)list_data(element))->vertex; if(match(pth_vertex, adj_vertex)) { relax(adjlist->vertex, pth_vertex, adj_vertex->weight); } } } i++; } /*將鄰接表結構鏈表中每一個黑色PathVertexx結構體插入鏈表paths中*/ list_init(paths,NULL); for(element=list_head(&graph_adjlists(graph)); element!=NULL; element=list_next(paths,NULL)) { /*加載鄰接表結構鏈表中的每個黑色頂點*/ pth_vertex=((AdjList *)list_data(element))->vertex; if(pth_vertex->color == black) { if(list_ins_next(paths, list_tali(paths), pth_vertex) != 0) { list_destroy(paths); return -1; } } } return 0; }
最短路徑算法在現實中一個很重要的應用是在互聯網中對數據進行路由。路由是將數據從一個點傳輸到另外一個點的決策過程。在互聯網中,路由是沿着相互鏈接的點(稱爲網關)傳播數據段或數據包的過程。在數據包經過一個網關時,路由器將會查看數據包最終目的地,而後將數據包發往下一個網關。路由器的目的就是將數據包往最接近於目的地的地方發送。
爲了將數據包往最接近目的地的地方發送,每一個路由器都要維護互聯網的結構信息或拓撲信息。這些信息存儲在路由表中。路由表爲每一個路由器知道如何到達的網關存儲一個條目。每一個條目指定把數據發送到下一個網關的地址。
因爲路由器會週期性的隨着互聯網的變化更新其路由表,所以數據包會盡量地沿着最佳路徑傳送數據。有一種類型的路由稱爲最短路徑優先路由或SPF路由,其中每一個路由器都維護有本身的網絡圖,以便它能經過計算自身與其餘結點之間的最短路徑來更新其路由表。互聯網拓撲圖是一個有向帶權圖,其頂點爲網關,邊爲網關之間的鏈接線。邊的權值由鏈接路徑的性能決定。偶爾,路由器會交換拓撲和性能的信息,爲此還專門設計了一種協議來完成此工做。
設計函數route,利用SPF路由算法計算更新路由表中條目所須要的信息。
該函數接受shortest的paths參數中返回的路徑信息列表。
它使用此信息來肯定路由器要把數據包發送到的下一個網關,以保證此網關離目的地更近了一步。
要爲指定的網關完成一個完整的表,首先要調用函數shortest,其中網關由start參數傳入。
接着,對於每一個路由表中包含的目的地址,調用函數route,其中的目的地址由destination傳入。
而後做爲從paths生成的路徑的圖graph_init中所提供的match函數,把此地址傳入match中。
route函數將目的列表paths中的父結點指針指向網關,同時返回一個傳送數據包的最佳結點(此結點存放在輸出參數next中)。next中返回的頂點指向paths中實際的頂點,因此只要還能訪問next,paths中的內存空間就必須有效。
下圖A部分展現了互聯網中處於a的路由器的路由表的計算過程。B部分展現了處理b的路由器計算路由表的過程。注意,依據在互聯網中起始位置的不一樣,其最短路徑也不一樣。一樣須要注意的是,在圖B中是沒辦法到達a的,因此在該表中也沒有相關條目。
計算路由的時間複雜度爲O(n2),其中n爲paths中的網關數目。
/*route.c*/ #include <stdlib.h> #include "graphalg.h" #include "list.h" #include "route.h" /*route*/ int route(List *paths, PathVertex *destination, PathVertex **next, int (*match)(const void *key1, const void *key2)) { PathVertex *temp, *parent; ListElmt *element; int found; /*查找位於網關鏈表中的目的地址*/ found = 0; for(element = list_head(paths); element != NULL; element = list_next(element)) { if(match(list_data(element),destination)) { temp = list_data(element); parent = ((PathVertex *)list_data(element))->parent; found = 1; break; } } /*如未發現目標地址,函數退出*/ if(!found) return -1; /*計算到目的地最短路徑的下一個網關*/ while(parent!=NULL) { temp = list_data(element); found = 0; for(element = list_head(paths); element != NULL; element = list_next(element)) { if(match(list_data(element),parent)) { parent = ((PathVertex *)list_data(element))->parent; found = 1; break; } } /*若是目標不能到達,函數退出*/ if(!found) return -1; } *next = temp; return 0; }