圖論:最短路徑搜索--Dijkstra算法(c代碼實現)


  最近因為辭職,有不少閑功夫,重溫下數據結構,順便練練手。今天說說最短路徑搜索算法中的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演示),然后就清楚了。

二:源代碼

  直接給源代碼,注釋很清楚,不解釋。

 

Dijkstra.h
 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
Dijkstra.c
  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 }
main.c
 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/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM