最短路徑算法是計算機網絡里一個常用的路由算法,該算法可以找出網絡中從一個節點到另一個節點的最短路徑。假設有一個網絡,其拓撲如下圖所示,圖中一共有8個節點,為節點A到節點H,相鄰節點間的距離標注在邊上,如節點A到節點B的距離為2。現在,假如從節點A出發,要到達節點D,最短路徑應該是怎樣呢?
圖1
1959年,Dijkstra提出了最短路徑算法,根據該算法,可以找出任意兩節點之間的最短網絡路徑。對圖1來說,從節點A到節點D的最短路徑應該是A->B->E->F->H->D。具體的過程如下所示:
圖2
首先,為每一個節點添加一個標簽(label),並全部初始化為(∞,-)。標簽左邊的數值表示從源節點(source node)到該節點最短路徑的長度,右邊的字符表示走最短路徑時該節點的上一個節點。接着,從節點A出發,搜索下一步可以到達的節點,在本例中是B和G,將B和G的標簽更新為(2,A)和(6,A),表示從A節點出發,到B節點的最短路徑長度為2,到G節點的最短路徑長度為6。然后,在所有的試探節點(tentative node)中,選出路徑長度最短的那一個,將其標注為永久節點(permanent node)。試探節點的標簽,左邊的數值為非無窮的一個數值,且沒有陰影。永久節點在圖中則一律加陰影顯示。試探節點代表了最短路徑的一種可能,就是說走最短路徑有可能會經過該節點,但也可能不經過,但是現在還無法確定,所以先用標簽記一下,記錄在目前所搜集到的信息下,從源節點到該節點最短需要走多長的路,但是這個值並不代表最后真實的最短路徑值,因為后面繼續搜索的話,可能會發現更短的路徑,這個試探節點的數值也會相應的更新為更小的那個數,所以這就是為什么會把它叫做試探節點。永久節點的標簽則不會再變,它的數值就代表了從源節點到該節點所有可能路徑中最短路徑的長度。每一步都會從所有試探節點中選出數值最小的那一個,然后將該試探節點轉為永久節點。在圖b中,試探節點有B和G,B的標簽數值為2,而C為6,所以選擇將B轉為永久節點。同理,在圖c中E和C都是試探節點,但是E的標簽數值為4,而B為9,所以選擇將數值更小的試探節點,即E,轉為永久節點。圖d中,有一點值得注意,G節點的標簽在c中的(6,A),為什么到了d會變成(5,E)呢?這是因為原來我們在第一次試探的時候,我們是從A直接走到G,這樣路程長度就為6。但是現在,我們在對E節點的下一步試探的時候,我們發現從E走到G只要走長度為1的路程,再加上從A走到E的路程,長度也才只有5,比從A直接到G還要短,所以我們認為從A走到G的最短路徑長度應該是5,而不是6,所以我們把G的標簽值給更新了。把G更新后,在圖d中,現有的試探節點有C、F、G這三個,其中G的標簽數值最小,所以選擇將G轉為永久節點。同理,在圖f中,發現從A走到F,再從F走到H的話,總共只要走長度為8的路程,比之前試探到的最短路徑還要短,所以把H的標簽從圖e的(9,G)更新為圖f的(8,F)。更新之后,圖f的試探節點有C和H,選擇數值更小的H轉為永久節點。這時,最近更新的永久節點為H,從H開始出發,搜索H下一步能到達的所有節點(不能是永久節點,因為已經固定了,不能再往回走),發現從H只能到D,將D的標簽更新為(10,H)。現在,試探節點只剩下了C和D,C的值比D的小,所以C轉為永久節點。最后,試探節點只剩下D,最近更新的永久節點為C,從C出發搜索下一步能到達的節點,只有D。從C到D長度為3,則從A到C再到D長度為12,比從A到H再到D要大,所以不更新D的標簽,還是按照原來的路走。然后從所有試探節點選出數值最小那個轉為永久節點,這里只有D,所以將D轉為永久節點。D一旦轉為永久節點,就意味着從A到D的最短路徑就此確定了。此時,往回推就能畫出一條最短路徑。如圖所示。
圖3
所以,從A到D最短的路徑為A->B->E->F->H->D。最短路徑算法代碼如下。
#define MAX_NODE 8
#define INFINITY 1000000000
int n, dist[MAX_NODE][MAX_NODE];
void shortest_path(int s, int t, int *path) { struct state{ int predecessor; int length; enum { permanent, tentative } label; }state[MAX_NODE]; int i, k, min; struct state *p; for (p = &state[0]; p < &state[n]; p++){ p->predecessor = -1; p->length = INFINITY; p->label = tentative; } state[t].length = 0; state[t].label = permanent; k = t; do{ for (i = 0; i < n; i++) if (dist[k][i] != 0 && state[i].label == tentative){ if (state[k].length + dist[k][i] < state[i].length){ state[i].predecessor = k; state[i].length = state[k].length + dist[k][i]; } } k = 0; min = INFINITY; for (i = 0; i < n; i++) if (state[i].label == tentative&&state[i].length < min){ min = state[i].length; k = i; } state[k].label = permanent; } while (k != s); i = 0; k = s; do{ path[i++] = k; k = state[k].predecessor; } while (k >= 0); }
參考文獻
[1] 計算機網絡(第5版)(P384~P387)[M],Andrew S. Tanenbaum,David J. Wetherall.