最近因為辭職,有不少閑功夫,重溫下數據結構,順便練練手。今天說說最短路徑搜索算法中的Dijkstra原理和實現。
一:簡介
這個算法用於解決圖中單源最短路徑問題。所謂單源節點是指給定源節點,求圖中其它節點到此源節點的最短路徑。如下圖所示:給定源節點a,求節點b到a的最短距離。
(圖來自於參考資料2)
那么如何尋找?還是以上圖為例:
1)初始化:設定除源節點以外的其它所有節點到源節點的距離為INFINITE(一個很大的數),且這些節點都沒被處理過。
2)從源節點出發,更新相鄰節點(圖中為2,3,6)到源節點的距離。然后在所有節點中選擇一個最段距離的點作為當前節點。
3)標記當前節點為done(表示已經被處理過),與步驟2類似,更新其相鄰節點的距離。(這些相鄰節點的距離更新也叫松弛,目的是讓它們與源節點的距離最小。因為你是在當前最小距離的基礎上進行更新的,由於當前節點到源節點的距離已經是最小的了,那么如果這些節點之前得到的距離比這個距離大的話,我們就更新它)。
4)步驟3做完以后,設置這個當前節點已被done,然后尋找下一個具有最小代價(cost)的點,作為新的當前節點,重復步驟3.
5)如果最后檢測到目標節點時,其周圍所有的節點都已被處理,那么目標節點與源節點的距離就是最小距離了。如果想看這個最小距離所經過的路徑,可以回溯,前提是你在步驟3里面加入了當前節點的最優路徑前驅節點信息。
看文字描述顯得蒼白無力,你可以結合上圖,看下這個視頻:http://v.youku.com/v_show/id_XMjQyOTY1NDQw.html (dijkstra演示),然后就清楚了。
二:源代碼
直接給源代碼,注釋很清楚,不解釋。

1 #ifndef _DIJKSTRA_H 2 #define _DIJKSTRA_H 3 4 #define MAX_VERTEX_NUM 100 //最大頂點數 5 #define MAX_EDGE_NUM 50 //相鄰最大節點數 6 #define INFINITE 1E5 //表示節點之間無連接的一個較大的數 7 #define MAX_STRLEN 256 //最大字符串字節數 8 9 #define FALSE 0 10 #define TRUE 1 11 typedef int BOOL; 12 typedef unsigned int UINT; 13 14 #define SAFEFREE(p) {if(NULL!=(p)) free(p);} 15 16 extern int g_node_num; //一個圖中,實際節點數的全局變量 17 typedef struct _vertex { //通用的頂點數據結構體 18 int id;//id 19 struct _vertex *pLinkList[MAX_EDGE_NUM]; //相鄰頂點的指針數組 20 int nCost[MAX_VERTEX_NUM]; //與相鄰頂點的權重數組 21 struct _vertex **next; //與剩余頂點之間的最短路徑 22 int *pmincost; //與剩余頂點之間的最小代價 23 }vertex; 24 25 typedef struct _node { //組成圖的頂點元素信息進行封裝 26 int nID; 27 struct _vertex *pVer; 28 }node; 29 30 BOOL InitGraphic(char path[], node arr[], UINT nsize); 31 void UnitGraphic(node arr[]); 32 void ViewGraphic(node arr[]); 33 BOOL Dijkstra(node arr[]); 34 void MinRoute(node arr[], UINT nSrID, UINT nDsID); 35 36 #endif

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include "Dijkstra.h" 5 6 int g_node_num;/*用於計算實際節點數的全局變量*/ 7 /**************************************** 8 *函數名:InitGraphic 9 *參數:path-圖的信息文件路徑;arr-儲存圖的數組;nsize-數組大小 10 *返回值:BOOL-成功返回1,錯誤返回0 11 *說明:根據圖的信息文件,初始化數組 12 *****************************************/ 13 BOOL InitGraphic(char path[], node arr[], UINT nsize) 14 { 15 char buf[MAX_STRLEN]; 16 char *pos; 17 char ctemp; 18 int ncost; 19 int i; 20 UINT nid;//臨時頂點ID 21 UINT ncid;//臨時連接頂點的ID 22 UINT nlinkpos;//連接頂點數組中的位置 23 24 memset(arr, 0, sizeof(node)*nsize);//賦值0 25 FILE *pfile = fopen(path, "r"); 26 if(NULL == pfile) { 27 printf("Error opening file.\n"); 28 return FALSE; 29 } 30 while(NULL != fgets(buf, MAX_STRLEN, pfile)) { 31 pos = strtok(buf, ":");//讀取一行,解析第一個冒號之前的標號,即第幾個節點 32 nid = atoi(pos); 33 if(nid < nsize) { 34 arr[nid-1].nID = nid; 35 arr[nid-1].pVer = (vertex*)malloc(sizeof(vertex));//申請一個頂點struct 36 if(NULL == arr[nid-1].pVer) { 37 printf("out of memory!\n"); 38 return FALSE; 39 } 40 memset(arr[nid-1].pVer, 0, sizeof(vertex));//賦值0 41 arr[nid-1].pVer->id = nid; 42 g_node_num++;//節點數加1 43 } else { 44 fprintf(stderr, "access the boundary of setting:%d\n", nid); 45 } 46 } 47 rewind(pfile);//文件指針跳轉到開始處,讀取各頂點的相鄰節點 48 for(i=0; i<g_node_num; i++) { 49 fscanf(pfile, "%d", &nid);//讀取第一個節點標號 50 nlinkpos = 0;//指示其相鄰節點結構體的當前位置 51 while((ctemp=fgetc(pfile)) != ';') { 52 fscanf(pfile, "%u-%d", &ncid, &ncost); 53 if(ncid > nsize || ncost < 0) { 54 fprintf(stderr, "access the boundary of setting or find negative cost:%u-%d\n", ncid, ncost); 55 return FALSE; 56 } 57 58 arr[nid-1].pVer->pLinkList[nlinkpos] = arr[ncid-1].pVer;/*相鄰節點指針數組賦值*/ 59 arr[nid-1].pVer->nCost[ncid-1] = ncost;/*此節點到相鄰節點的cost*/ 60 arr[nid-1].pVer->pmincost = NULL; 61 arr[nid-1].pVer->next = NULL; 62 nlinkpos++; 63 } 64 } 65 fclose(pfile); 66 return TRUE; 67 } 68 /******************************************* 69 *函數名:ViewGraphic 70 *參數:arr-圖的數組 71 *返回值:無 72 *說明:打印圖的結構信息 73 *******************************************/ 74 void ViewGraphic(node arr[]) 75 { 76 int i, j; 77 int nidtemp;//臨時節點序號 78 printf("\nID\tConnceted to-ID:cost"); 79 for(i=0; i<g_node_num; i++) { 80 printf("\n%d\t",arr[i].nID); 81 for(j=0; arr[i].pVer->pLinkList[j] != NULL; j++) { 82 nidtemp = arr[i].pVer->pLinkList[j]->id; 83 printf("%d:", nidtemp); 84 printf("%d ",arr[i].pVer->nCost[nidtemp-1]); 85 } 86 } 87 } 88 /************************************************* 89 *函數名:Dijkstra 90 *參數:arr-圖的數組 91 *返回值:TRUE-成功;FALSE-失敗 92 *說明:依次將每個節點作為起始節點,計算剩余節點與其之間的最短路徑 93 *************************************************/ 94 BOOL Dijkstra(node arr[]) 95 { 96 UINT i, j, k; 97 vertex *pbegin, *ptemp, *ptemp1; 98 int *tcost;//用於儲存其余節點到起始節點的最小代價 99 BOOL *pbDone;//用於判斷節點是否計算完畢的數組 100 int nidtemp;//與當前節點相鄰的其它節點中,cost最小的頂點序號 101 int nmixcost = INFINITE; 102 103 tcost = (int*)malloc(g_node_num * sizeof(int)); 104 pbDone = (BOOL*)malloc(g_node_num * sizeof(BOOL)); 105 if(NULL == tcost || NULL == pbDone) { 106 printf("out of memory\n"); 107 return FALSE; 108 } 109 for(i=0; arr[i].pVer!=0; i++) {//依次將每個頂點作為起始節點 110 for(j=0; j<g_node_num; j++) {//初始化數組 111 tcost[j] = INFINITE;//其它節點到起始節點的代價 112 pbDone[j] = 0; 113 } 114 pbegin = arr[i].pVer;//起始頂點 115 pbegin->next = (vertex**)malloc(g_node_num * sizeof(vertex*));//儲存每個頂點最優的前驅頂點的id的數組 116 pbegin->pmincost = (int*)malloc(g_node_num * sizeof(int));//儲存每個頂點到起始頂點的最小代價數組 117 tcost[i] = 0;//初始化 118 pbDone[i] = 1; 119 pbegin->pmincost[i] = 0; 120 ptemp = pbegin;//設定起始頂點為當前頂點 121 122 while(1) { 123 for(j=0; ptemp->pLinkList[j]!=0; j++) {//遍歷當前頂點的相鄰節點,更新最小代價(松弛邊) 124 ptemp1 = ptemp->pLinkList[j]; 125 if(tcost[ptemp1->id-1] > tcost[ptemp->id-1] + ptemp->nCost[ptemp1->id-1] \ 126 && pbDone[ptemp1->id-1] == 0) { 127 tcost[ptemp1->id-1] = tcost[ptemp->id-1] + ptemp->nCost[ptemp1->id-1]; 128 pbegin->next[ptemp1->id-1] = ptemp;//設定頂點更新后的前驅頂點 129 } 130 } 131 nmixcost = INFINITE; 132 for(j=0; j<g_node_num; j++) {//找出更新后,所有頂點中,代價最小的頂點,重新作為當前頂點。這一步可以優化。 133 if(pbDone[arr[j].nID-1] != 1 && tcost[arr[j].nID-1] < nmixcost && tcost[arr[j].nID-1] != 0) { 134 nmixcost = tcost[arr[j].nID-1]; 135 nidtemp = arr[j].nID; 136 } 137 } 138 if(nmixcost == INFINITE) {//除起始頂點外的所有節點都已經被處理完畢,退出 139 break; 140 } 141 pbegin->pmincost[nidtemp-1] = nmixcost; 142 ptemp = arr[nidtemp-1].pVer;//重新設定當前頂點 143 pbDone[nidtemp-1] = 1;//表示當前頂點已經被處理過了,其路徑已經最短,代價最小 144 } 145 } 146 free(pbDone); 147 free(tcost); 148 return TRUE; 149 } 150 /********************************************************** 151 *函數名:MinRoute 152 *參數:arr-圖的數組;nSrID-起始節點序號;nDsID-目的節點序號 153 *返回值:無 154 *說明:給定圖的數組,利用Dijkstra函數處理之后,根據設定的起始和終止節點序號,打印 155 *最短路徑和最小代價。 156 ***********************************************************/ 157 void MinRoute(node arr[], UINT nSrID, UINT nDsID) 158 { 159 if(nSrID<0 || nSrID>g_node_num || nDsID<0 || nDsID>g_node_num) { 160 printf("Invalid node number!\n"); 161 } 162 int nid; 163 vertex *ptemp = arr[nSrID-1].pVer; 164 printf("the total cost is: %d\n", ptemp->pmincost[nDsID-1]); 165 printf("the path is:"); 166 nid = nDsID; 167 printf("%d->",arr[nid-1].nID); 168 while(ptemp->next[nid-1]->id != arr[nSrID-1].nID) { 169 nid = ptemp->next[nid-1]->id;//回溯路徑 170 printf("%d->",nid); 171 } 172 printf("%d\n",arr[nSrID-1]); 173 } 174 /***************************************** 175 *函數名:UnitGraphic 176 *參數:arr-圖的數組 177 *返回值:無 178 *說明:釋放內存 179 *****************************************/ 180 void UnitGraphic(node arr[]) 181 { 182 UINT i; 183 for(i=0; i<g_node_num; i++) { 184 if(arr[i].pVer != NULL) { 185 SAFEFREE(arr[i].pVer->next); 186 SAFEFREE(arr[i].pVer->pmincost); 187 } 188 } 189 }

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include "Dijkstra.h" 5 6 int main(int argc, char *argv[]) 7 { 8 char filepath[MAX_STRLEN];//圖的信息文件 9 node graphic[MAX_VERTEX_NUM] = {0};//圖的數組 10 int sid, did; 11 int selnum; 12 13 if(argc < 2) { 14 printf("usage:*.exe input\n"); 15 exit(1); 16 } 17 strcpy(filepath, argv[1]); 18 /***********初始化圖***************/ 19 if(!InitGraphic(filepath, graphic, MAX_VERTEX_NUM)) { 20 UnitGraphic(graphic); 21 exit(1); 22 } 23 printf("**** Print The Graphic information ****"); 24 ViewGraphic(graphic);//打印圖 25 /************dijkstra運算***********/ 26 if(!Dijkstra(graphic)) { 27 UnitGraphic(graphic); 28 exit(1); 29 } 30 31 printf("\n****Find the shortest path between nodes****"); 32 printf("\n1.View minimum route between nodes."); 33 printf("\n2.Exit."); 34 35 for(;;) { 36 printf("\nEnter Your Selection:"); 37 scanf("%d",&selnum); 38 switch(selnum) { 39 case 1: { 40 printf("\nEnter source node ID:"); 41 scanf("%d",&sid); 42 printf("\nEnter destination node ID:"); 43 scanf("%d",&did); 44 45 MinRoute(graphic, sid, did);//打印最優路徑 46 break; 47 } 48 case 2: 49 exit(1); 50 default: { 51 printf("\nEnter proper choice.\n"); 52 break; 53 } 54 } 55 } 56 UnitGraphic(graphic); 57 return 0; 58 }
demo:
輸入文件內容:
1:2-2:3-4:5-4;
2:1-2:3-3;
3:1-4:2-3:5-6:6-7;
4:6-8;
5:1-4:3-6;
6:3-7:4-8;
格式說明:
起始節點:連接節點1-權值:連接節點2-權值:連接節點3-權值.....
圖的結構:
運行結果:
三:總結
Dijkstra最短路徑搜索屬於廣度優先搜索(BFS, Breadth-First-Search),即不斷去搜索當前節點的所有相鄰節點,並更新它們的cost。更新的前提是認為:當前節點是目前與起始節點之間cost最小的節點,它認為自己是最優解,要想到達目的節點,經過我這里必然錯不了,接着在此基礎上不斷去尋找其它最優路徑,運用的是一種貪婪算法的思想。但是有時候並不是最優解,典型的例子就是:最小數目找零的例子,現有10元,5元,1元的紙幣,如果要找15塊錢,貪婪算法的結果是-10元+5元。但是如果現在假設銀行發行了12元一張的紙幣(銀行閑的蛋疼),還用貪婪算法,結果是12+1+1+1(坑爹的,找這么多硬幣!!)。但是實際上最優解仍然是10元+5元。所以有時候,具體問題要具體分析。另外,最優路徑搜素還有帶有啟發性的A*搜索,雙向廣度優先搜索(BFS),它們比Dijkstra算法的搜索效率要高。改天再續。
再說一下,我的代碼中,在尋找下一個當前節點時,用了全局搜索,這顯然是一個很笨的方法,復雜度太高。一般的方法都是定義一個開集,一個閉集,用來存儲未處理過的節點和已被處理的節點,所以我們可以用FIFO隊列去優化。參考資料1。
參考資料:
1,《數據結構與算法分析-c++描述》,weiss
2,http://en.wikipedia.org/wiki/Dijkstra's_algorithm
3,http://blog.chinaunix.net/uid-20662820-id-142445.html
4,http://www.rawbytes.com/dijkstras-algorithm-in-c/