一:最短路徑問題
(一)定義
在網絡中,求兩個不同頂點之間的所有路徑中,邊的權值之和最小的那條路徑
1.這條路徑就是兩點之間的最短路徑 2.第一個頂點為源點 3.最后一個頂點終點
(二)分類
單源最短路徑--->有權,無權--->有向,無向
從某固定源點觸發,求其到所有其他頂點的最短路徑
多源最短路徑
求任意兩頂點間的最短路徑
可以通過對每個頂點使用一次單源(不是最好)
二:無權圖的單源最短路徑(有向)
不考慮無向,無向我們使用BFS,進行層次遍歷時,就可以獲取
(一)定義
按照遞增(非遞減)的順序找出各個頂點的最短路徑
找出視圖源點v3到每個頂點的最短路徑
(二)思考
從上圖路徑表我們可以看出,其路徑是按照BFS(有所不同),使用隊列進行遞增訪問各個頂點,從而遍歷了所有頂點。
注意:這里我們不使用棧來實現,因為棧用到回溯法,而且使用棧不能很好找到最短路徑長
(三)代碼實現
創建鄰接矩陣時看這個圖 進行結果對比用這個
void unWeight(MGraph G, int s) { int dist[MAXVEX]; //記錄達到下標對應頂點的最小距離 int path[MAXVEX]; //記錄每個下標對應頂點的前一個經過的頂點 int i, v, w; //生成隊列一會使用 LinkQueue Q; InitQueue(&Q); for (i = 0; i < MAXVEX; i++) dist[i] = -1; //全部初始化為-1,表示該頂點未被訪問過,沒有找到最短路徑到這個頂點 //將源點入隊 EnQueue(&Q, s); dist[s] = 0; path[s] = s; //將這里設置為他自己是自己的上一步,因為后面根本不會去設置他了 while (!EmptyQueue(Q)) { DeQueue(&Q, &v); for (w = 0; w < G.numVertexes; w++) { if (G.arc[v][w] == 1) //找到鄰接點w { if (dist[w] == -1) { dist[w] = dist[v] + 1; path[w] = v; EnQueue(&Q, w); } } } } for (i = 0; dist[i] != -1; i++) //對各個頂點的最短路徑長度進行打印,以及他的上一步路徑也打印 { printf("%d %c-%c\n", dist[i], G.vers[path[i]], G.vers[i]); } }
(四)全部代碼

#pragma once #ifndef _QUEUE_H #define _QUEUE_H #include <stdio.h> #include <stdlib.h> #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 100 typedef int ElemType; typedef int Status; typedef struct _qNode { ElemType data; struct _qNode* next; }QNode,*QNodePtr; typedef struct { QNodePtr front,rear; //隊頭隊尾指針 }LinkQueue; Status InitQueue(LinkQueue* Q); Status EnQueue(LinkQueue* Q, ElemType e); Status DeQueue(LinkQueue* Q, ElemType* e); Status EmptyQueue(LinkQueue Q); Status getHead(LinkQueue Q,ElemType* e); #endif

#include "queue.h" Status InitQueue(LinkQueue* Q) { if (!Q) return ERROR; Q->front = Q->rear = (QNodePtr)malloc(sizeof(QNode)); if (!Q->front) return ERROR; Q->front->next = NULL; return OK; } Status EnQueue(LinkQueue* Q, ElemType e) { //尾插法 if (!Q) return ERROR; QNodePtr q = (QNodePtr)malloc(sizeof(QNode)); if (!q) return ERROR; q->data = e; q->next = (*Q).rear->next; (*Q).rear->next = q; Q->rear = q; return OK; } Status DeQueue(LinkQueue* Q, ElemType* e) { QNodePtr q; if (!Q || !e || EmptyQueue(*Q)) return ERROR; q = Q->front->next; Q->front->next = q->next; *e = q->data; if (Q->rear == q) Q->rear = Q->front; free(q); return OK; } Status EmptyQueue(LinkQueue Q) { if (!Q.front->next) return TRUE; return FALSE; } Status getHead(LinkQueue Q,ElemType* e) { QNodePtr q; if (EmptyQueue(Q)) return ERROR; q = Q.front->next; *e = q->data; return OK; }

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include "queue.h" #define MAXVEX 100 //最大頂點數 #define INFINITY 65535 //用0表示∞ typedef char VertexType; //頂點類型,字符型A,B,C,D... typedef int EdgeType; //邊上權值類型10,15,... //鄰接矩陣結構 typedef struct { VertexType vers[MAXVEX]; //頂點表 EdgeType arc[MAXVEX][MAXVEX]; //鄰接矩陣,可看作邊表 int numVertexes, numEdges; //圖中當前的頂點數和邊數 }MGraph; //創建鄰接矩陣 void CreateMGraph(MGraph* G); //顯示鄰接矩陣 void showGraph(MGraph G); //進行最小路徑獲取 void unWeight(MGraph G); int main() { MGraph MG; CreateMGraph(&MG); showGraph(MG); unWeight(MG,2); system("pause"); return 0; } //生成鄰接矩陣 void CreateMGraph(MGraph* G) { int i, j, k, w; G->numVertexes = 7; G->numEdges = 12; //讀入頂點信息 G->vers[0] = 'A'; G->vers[1] = 'B'; G->vers[2] = 'C'; G->vers[3] = 'D'; G->vers[4] = 'E'; G->vers[5] = 'F'; G->vers[6] = 'G'; G->vers[7] = 'H'; G->vers[8] = 'I'; //getchar(); //可以獲取回車符 for (i = 0; i < G->numVertexes; i++) for (j = 0; j < G->numVertexes; j++) G->arc[i][j] = INFINITY; //鄰接矩陣初始化 //創建了有向鄰接矩陣 G->arc[0][1] = 1; G->arc[0][3] = 1; G->arc[1][3] = 1; G->arc[1][4] = 1; G->arc[2][0] = 1; G->arc[2][5] = 1; G->arc[3][2] = 1; G->arc[3][4] = 1; G->arc[3][5] = 1; G->arc[3][6] = 1; G->arc[4][6] = 1; G->arc[6][5] = 1; } //顯示鄰接矩陣 void showGraph(MGraph G) { for (int i = 0; i < G.numVertexes; i++) { for (int j = 0; j < G.numVertexes; j++) { if (G.arc[i][j] != INFINITY) printf("%5d", G.arc[i][j]); else printf(" 0"); } printf("\n"); } } void unWeight(MGraph G, int s) { int dist[MAXVEX]; //記錄達到下標對應頂點的最小距離 int path[MAXVEX]; //記錄每個下標對應頂點的前一個經過的頂點 int i, v, w; //生成隊列一會使用 LinkQueue Q; InitQueue(&Q); for (i = 0; i < MAXVEX; i++) dist[i] = -1; //全部初始化為-1,表示該頂點未被訪問過,沒有找到最短路徑到這個頂點 //將源點入隊 EnQueue(&Q, s); dist[s] = 0; path[s] = s; //將這里設置為他自己是自己的上一步,因為后面根本不會去設置他了 while (!EmptyQueue(Q)) { DeQueue(&Q, &v); for (w = 0; w < G.numVertexes; w++) { if (G.arc[v][w] == 1) //找到鄰接點w { if (dist[w] == -1) { dist[w] = dist[v] + 1; path[w] = v; EnQueue(&Q, w); } } } } for (i = 0; dist[i] != -1; i++) { printf("%d %c-%c\n", dist[i], G.vers[path[i]], G.vers[i]); } }
三:有權的單源最短路徑算法(迪傑斯特拉Dijkstra算法)
(一)了解
從v1-v6最小為6,即v1-v4-v7-v6。不一定為經過頂點最小的路,和上面的無權最短路徑不同
注意:我們不考慮負值圈
會導致一直循環,獲取無窮收益。導致所有算法都失效
(二)解決方法
方法和上面的無權路徑還是相似的,就是按照遞增的順序找出各個頂點的最短路
(三)迪傑斯特拉Dijkstra算法

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include "queue.h" #define MAXVEX 100 //最大頂點數 #define INFINITY 65535 //用0表示∞ typedef char VertexType; //頂點類型,字符型A,B,C,D... typedef int EdgeType; //邊上權值類型10,15,... //鄰接矩陣結構 typedef struct { VertexType vers[MAXVEX]; //頂點表 EdgeType arc[MAXVEX][MAXVEX]; //鄰接矩陣,可看作邊表 int numVertexes, numEdges; //圖中當前的頂點數和邊數 }MGraph; //創建鄰接矩陣 void CreateMGraph(MGraph* G); //顯示鄰接矩陣 void showGraph(MGraph G); //迪卡斯特拉算法,獲取最短路徑 void Dijkatra(MGraph G, int s); void Dijkatra(MGraph G,int s) { int path[MAXVEX]; //是數組下標表示的頂點所經歷的前一個頂點 int dist[MAXVEX]; //是數組下標表示的頂點的最小權值路徑和 //上面兩個數組都有作用,和無權最短路徑一致,但是無權最短路徑可以使用dist是否被設置來判斷一個頂點是否被訪問, //但是這里無法使用,因為dist和普里姆算法中的lowcost一樣,是使用貪心算法時,每到一個頂點,我們都會全部更新dist //所以我們需要另外一個數組來標志各個頂點是否被訪問 int final[MAXVEX]; int i,j,k,min; //對數據進行初始化 for (i = 0; i < G.numVertexes;i++) { final[i] = 0; //0表示該數組下標所表示的頂點未被訪問 path[i] = 0; //初始化路徑數組為0,表示當前每個都是獨立的根節點 dist[i] = G.arc[s][i]; //這一步是重點:初始化路徑數組的值為起始v0到各個點的權值 } dist[s] = 0; //到源點自己的路徑為0 path[s] = s; //設置源點的前一個頂點就是自己 final[s] = 1; //源點被訪問過了 //開始主循環,每次求的v0(s)到某個v頂點的最短路徑 for (i = 0; i < G.numVertexes;i++) { min = INFINITY; //和普里姆算法相似 for (j = 0; j < G.numVertexes;j++) //由於是有向圖所以都要從0開始,找到他的每個鄰接點 { if (!final[j]&&dist[j]<min) //若是該頂點沒有被訪問過,且該點到s點的距離小於min,我們就將min設置為他 { k = j; //記錄下該v到s點的下標和min最小路徑 min = dist[j]; } } final[k] = 1; //將目前找到的距離v0(S)最近的頂點置為1 for (j = 0; j < G.numVertexes;j++) //修正當前最短路徑即距離 { //修正方法就是循環k的每個鄰接點,我們作為三角形來看,若是兩邊之和小於第三邊,那我們原來找的那條直接的最短邊就失效了,用這兩條直接代替 //所以我們將距離修改,路徑設置為他的上一步k, if (!final[j]&&(min+G.arc[k][j])<dist[j]) { //說明找到了更短的路徑,修改dist和path數組 dist[j] = min + G.arc[k][j]; //修改當前路徑長度 path[j] = k; } } } for (i = 0; i<G.numVertexes; i++) { printf("%d %c-%c\n", dist[i], G.vers[path[i]], G.vers[i]); } } int main() { MGraph MG; CreateMGraph(&MG); showGraph(MG); Dijkatra(MG,0); system("pause"); return 0; } //生成鄰接矩陣 void CreateMGraph(MGraph* G) { int i, j, k, w; G->numVertexes = 7; G->numEdges = 12; //讀入頂點信息 G->vers[0] = 'A'; G->vers[1] = 'B'; G->vers[2] = 'C'; G->vers[3] = 'D'; G->vers[4] = 'E'; G->vers[5] = 'F'; G->vers[6] = 'G'; G->vers[7] = 'H'; G->vers[8] = 'I'; //getchar(); //可以獲取回車符 for (i = 0; i < G->numVertexes; i++) for (j = 0; j < G->numVertexes; j++) G->arc[i][j] = INFINITY; //鄰接矩陣初始化 //創建了有向鄰接矩陣 G->arc[0][1] = 2; G->arc[0][3] = 1; G->arc[1][3] = 3; G->arc[1][4] = 10; G->arc[2][0] = 4; G->arc[2][5] = 5; G->arc[3][2] = 2; G->arc[3][4] = 2; G->arc[3][5] = 8; G->arc[3][6] = 4; G->arc[4][6] = 6; G->arc[6][5] = 1; } //顯示鄰接矩陣 void showGraph(MGraph G) { for (int i = 0; i < G.numVertexes; i++) { for (int j = 0; j < G.numVertexes; j++) { if (G.arc[i][j] != INFINITY) printf("%5d", G.arc[i][j]); else printf(" 0"); } printf("\n"); } }
void Dijkatra(MGraph G,int s) { int path[MAXVEX]; //是數組下標表示的頂點所經歷的前一個頂點 int dist[MAXVEX]; //是數組下標表示的頂點的最小權值路徑和 //上面兩個數組都有作用,和無權最短路徑一致,但是無權最短路徑可以使用dist是否被設置來判斷一個頂點是否被訪問, //但是這里無法使用,因為dist和普里姆算法中的lowcost一樣,是使用貪心算法時,每到一個頂點,我們都會全部更新dist //所以我們需要另外一個數組來標志各個頂點是否被訪問 int final[MAXVEX]; int i,j,k,min; //對數據進行初始化 for (i = 0; i < G.numVertexes;i++) { final[i] = 0; //0表示該數組下標所表示的頂點未被訪問 path[i] = 0; //初始化路徑數組為0,表示當前每個都是獨立的根節點 dist[i] = G.arc[s][i]; //這一步是重點:初始化路徑數組的值為起始v0到各個點的權值 } dist[s] = 0; //到源點自己的路徑為0 path[s] = s; //設置源點的前一個頂點就是自己 final[s] = 1; //源點被訪問過了 //開始主循環,每次求的v0(s)到某個v頂點的最短路徑 for (i = 0; i < G.numVertexes;i++) { min = INFINITY; //和普里姆算法相似 for (j = 0; j < G.numVertexes;j++) //由於是有向圖所以都要從0開始,找到他的每個鄰接點 { if (!final[j]&&dist[j]<min) //若是該頂點沒有被訪問過,且該點到s點的距離小於min,我們就將min設置為他 { k = j; //記錄下該v到s點的下標和min最小路徑 min = dist[j]; } } final[k] = 1; //將目前找到的距離v0(S)最近的頂點置為1 for (j = 0; j < G.numVertexes;j++) //修正當前最短路徑即距離 { //修正方法就是循環k的每個鄰接點,我們作為三角形來看,若是兩邊之和小於第三邊,那我們原來找的那條直接的最短邊就失效了,用這兩條直接代替 //所以我們將距離修改,路徑設置為他的上一步k, if (!final[j]&&(min+G.arc[k][j])<dist[j]) { //說明找到了更短的路徑,修改dist和path數組 dist[j] = min + G.arc[k][j]; //修改當前路徑長度 path[j] = k; } } } for (i = 0; i<G.numVertexes; i++) { printf("%d %c-%c\n", dist[i], G.vers[path[i]], G.vers[i]); } }
解釋:
迪傑斯特拉算法和普里姆算法和上面的無權最短路徑算法相似,前兩個紅線處也是重點。自己想想。
下面來看第三處
for (j = 0; j < G.numVertexes;j++) //修正當前最短路徑即距離 { //修正方法就是循環k的每個鄰接點,我們作為三角形來看,若是兩邊之和小於第三邊,那我們原來找的那條直接的最短邊就失效了,用這兩條直接代替 //所以我們將距離修改,路徑設置為他的上一步k, if (!final[j]&&(min+G.arc[k][j])<dist[j]) { //說明找到了更短的路徑,修改dist和path數組 dist[j] = min + G.arc[k][j]; //修改當前路徑長度 path[j] = k; } }
我們選取源點的第一次循環來講解
1.首先:我們前面的代碼已經確定了源點(0)的最短路徑
例如上圖:我們確定了v0點的最短距離是v0-v3是1,所以我們將final[3]=1
2.我們在第三處,for循環中,修正的最短距離,不是我們v3距離,而是我們v3的鄰接點的最短距離。
原來我們的dist是:
現在我們的for循環將去修正他,修正的方法是:
因為v1未被標記,而且min(就是d(v0-v3))+d(v3-v1)=1+3大於原來的dist[1]=2,所以不予理會
因為v2未被標記,而且min(就是d(v0-v3))+d(v3-v2)=1+2小於原來的dist[2]=4,所以我們將他的距離修改,變為dist[2]=min+E(3,2),將他的路徑也做修正,他的是一個頂點變為v3,path[2]=3
修正后的dist數組是:
for (j = 0; j < G.numVertexes;j++) //修正當前最短路徑即距離 { //修正方法就是循環k的每個鄰接點,我們作為三角形來看,若是兩邊之和小於第三邊,那我們原來找的那條直接的最短邊就失效了,用這兩條直接代替 //所以我們將距離修改,路徑設置為他的上一步k, if (!final[j]&&(min+G.arc[k][j])<dist[j]) { //說明找到了更短的路徑,修改dist和path數組 dist[j] = min + G.arc[k][j]; //修改當前路徑長度 path[j] = k; } }
最后:說一句
有向圖和無向圖無非就是矩陣不對稱而已,求最短路徑幾乎是一致的。所以不必考慮太多
Dijkstra算法解決了從某個頂點到其余各頂點的最短路徑。其時間復雜度是O(n*2)