資料結構與演算法-最短路徑Dijkstra和Floy演算法
最短路徑問題一般分為兩種情況,單源最短路徑(即從一個點出發到其餘各點的最短路徑問題)和每對頂點之間的最短路徑問題。Dijkstra和Floy演算法相比之下我更喜歡Floy演算法,該演算法容易理解,思路簡潔。
兩種演算法解決最短路徑都是基於貪心的演算法,從區域性出發一點點擴充套件。
以一個簡單的例子來說,如下圖,圖中有6點,可以看做是6個城市,求各城市之間的最短路徑問題。這裡圖中只有6個頂點,你可能僅憑眼看就能看出來最短路徑,就是一個暴力列舉解決的過程,但是當結點數增多,列舉效率是很慢很耗時的,還有如何將眼睛看得到最短路徑的結果這個過程抽象成程式碼,然後程式設計解決,是很考察一個人的抽象能力、問題建模。
Dijkstra演算法把這個圖裡面的所有頂點分成兩個集合S和V-S,集合S中存放已經找到最短路徑的頂點,V-S中存放當前還未找到最短路徑的頂點。這裡就體現貪心的思想了,當我去解決一個還未找到最短路徑的頂點時,我可以在已經找到最短路徑的集合裡面找這些頂點是否有達到未找到最短路徑的頂點的路徑,這樣子的走法是否比我直接走那個點近。演算法按照最短路徑長度遞增的順序逐個將V-S集合中的元素加入到S集合中來,直到所有的元素都加入到集合中,就完成了問題的求解。
下一個待解決頂點的最短路徑只有兩種可能:
1、直接從出發點到該點。
2、從出發點經過以求得最短路徑的某個點,再到達待解決頂點,由兩段路線組成。
這是這個演算法的主要思想。大概的流程如下
核心演算法:
void Dijkstra(const GRAPH *graph, int *dist) { int min; int k; int j; int t; int i; int *flag; flag = (int *)calloc(sizeof(int), graph->vexnum); for(j = 1, flag[0] = 1; j < graph->vexnum; j++) { dist[j] = graph->arc[0][j]; } for(t = 1; t < graph->vexnum; t++) { min = INFINITY; k = 0; // 找到當前記錄最短路徑的陣列dist 中最小的路徑 還有達到的結點 for(i = 1; i < graph->vexnum; i++) { if((flag[i] == 0) && (dist[i] < min)) { min = dist[i]; k = i; } } flag[k] = 1; // 以這個找到的k結點 以這個結點為中間結點出發去判斷 通過該節點達到其他結點是夠更近 for(i = 1; i < graph->vexnum; i++) { // 在這裡如果發現了 從已經找到最短路徑的節點間接達到待解決點 要更近如果有這樣的路徑 // 就更新記錄最短路徑的陣列 if((flag[i] == 0) && (dist[k] + graph->arc[k][i] <= dist[i])) { dist[i] = dist[k] + graph->arc[k][i]; } } } }
Floy演算法比起上面的演算法理解起來要簡單的多,該演算法的時間複雜度是O(N^3),可以解決各頂點之間的最短路徑。而Dijkstra解決的是一個頂點到其他頂點的問題,時間複雜度是O(N^2),若要用Dijkstra演算法解決各頂點之間的最短路徑問題,外層再加上一層迴圈,總的時間複雜度也是O(N^3)。所以兩個演算法時間複雜度都差不多。
FLoy演算法引入一個和圖一樣大小的矩陣,叫做路由矩陣,來表示達到目標節點的前一個經過的結點。
這個演算法的整體思想就是,比如現在有結點A,B,C三個結點,當以A為中間結點的時候,判斷B->C還是B->A->C更近。然後繼續以B,C為中間結點,做同樣的操作。
演算法過程如下。
核心程式碼:
void Floy(const GRAPH *graph, int **Map_info, int **rount_matrix) {
int i;
int j;
int k;
for(i = 0; i < graph->vexnum; i++) {
for(j = 0; j < graph->vexnum; j++) {
Map_info[i][j] = graph->arc[i][j];
rount_matrix[i][j] = j;
}
}
for(i = 0; i < graph->vexnum; i++) {
for(j = 0; j < graph->vexnum; j++) {
for(k = 0; k < graph->vexnum; k++) {
if(Map_info[j][k] > Map_info[j][i] + Map_info[i][k]) {
Map_info[j][k] = Map_info[j][i] + Map_info[i][k];
rount_matrix[j][k] = i;
}
}
}
}
}
附上兩個演算法的完整程式碼和結果
#include<stdio.h>
#include<malloc.h>
#include"../../include/kwenarraytools.h"
#define MAXVEXNUM 100
#define INFINITY 99999
typedef struct GRAPH{
int vexnum; //頂點個數
int edgenum; //邊的個數
int arc[MAXVEXNUM][MAXVEXNUM]; //圖的鄰接矩陣
char *vec_infor; //記錄各個頂點的資訊 長度和vexnum保持一致
}GRAPH;
void createGraph(GRAPH **graph, int vexnum, int edgenum);
void destoryGraph(GRAPH **graph);
void Dijkstra(const GRAPH *graph, int *dist);
void showMatrix_1(int array[][MAXVEXNUM], int n);
//map_info記錄的各點到各點的距離資訊, rout_matrix是路由矩陣 達到目標點最後一次經過的結點
void Floy(const GRAPH *graph, int **Map_info, int **rount_matrix);
void showArray(int *ArrayFirstAddress, int array_length);
void Floy(const GRAPH *graph, int **Map_info, int **rount_matrix) {
int i;
int j;
int k;
for(i = 0; i < graph->vexnum; i++) {
for(j = 0; j < graph->vexnum; j++) {
Map_info[i][j] = graph->arc[i][j];
rount_matrix[i][j] = j;
}
}
for(i = 0; i < graph->vexnum; i++) {
for(j = 0; j < graph->vexnum; j++) {
for(k = 0; k < graph->vexnum; k++) {
if(Map_info[j][k] > Map_info[j][i] + Map_info[i][k]) {
Map_info[j][k] = Map_info[j][i] + Map_info[i][k];
rount_matrix[j][k] = i;
}
}
}
}
}
void Dijkstra(const GRAPH *graph, int *dist) {
int min;
int k;
int j;
int t;
int i;
int *flag;
flag = (int *)calloc(sizeof(int), graph->vexnum);
for(j = 1, flag[0] = 1; j < graph->vexnum; j++) {
dist[j] = graph->arc[0][j];
}
for(t = 1; t < graph->vexnum; t++) {
min = INFINITY;
k = 0;
// 找到當前記錄最短路徑的陣列dist 中最小的路徑 還有達到的結點
for(i = 1; i < graph->vexnum; i++) {
if((flag[i] == 0) && (dist[i] < min)) {
min = dist[i];
k = i;
}
}
flag[k] = 1;
// 以這個找到的k結點 以這個結點為中間結點出發去判斷 通過該節點達到其他結點是夠更近
for(i = 1; i < graph->vexnum; i++) {
// 在這裡如果發現了 從已經找到最短路徑的節點間接達到待解決點 要更近如果有這樣的路徑
// 就更新記錄最短路徑的陣列
if((flag[i] == 0) && (dist[k] + graph->arc[k][i] <= dist[i])) {
dist[i] = dist[k] + graph->arc[k][i];
}
}
}
}
void destoryGraph(GRAPH **graph) {
if((*graph) == NULL) {
return;
}
free((*graph)->vec_infor);
free(*graph);
*graph = NULL;
}
void createGraph(GRAPH **graph, int vexnum, int edgenum) {
int i;
int data;
int from;
int to;
int j;
*graph = (GRAPH *)calloc(sizeof(GRAPH), 1);
(*graph)->vexnum = vexnum;
(*graph)->edgenum = edgenum;
(*graph)->vec_infor = (char *)calloc(sizeof(char), 1);
for(i = 0; i < vexnum; i++) {
printf("input the %d/%d vex infor: ", i+1, vexnum);
setbuf(stdin, NULL);
(*graph)->vec_infor[i] = getchar();
}
for(i = 0; i < vexnum; i++) {
for(j = 0; j < vexnum; j++) {
(*graph)->arc[i][j] = INFINITY;
}
}
for(i = 0; i < vexnum; i++) {
(*graph)->arc[i][i] = 0;
}
for(i = 0; i < edgenum; i++) {
printf("input the %d/%d ede infor(from to power):", i+1, edgenum);
setbuf(stdin, NULL);
scanf("%d%d%d", &from, &to, &data);
// printf("from = %d, to = %d, data = %d\n", from, to, data);
(*graph)->arc[from][to] = (*graph)->arc[to][from]= data;
}
}
int main(void) {
GRAPH *graph = NULL;
int *dij_dist;
int vexnum = 4; //頂點個數
int edgenum = 5; //邊數
int **Floy_result_matrix;
int **Floy_rount_matrix;
printf("input vexnum and edgenum: ");
scanf("%d%d", &vexnum, &edgenum);
dij_dist = (int *)calloc(sizeof(int), vexnum);
createGraph(&graph, vexnum, edgenum);
printf("Map Matrix infor:\n");
showMatrix_1(graph->arc, vexnum);
Dijkstra(graph, dij_dist);
printf("Dijkstra result: \n");
showArray(dij_dist, vexnum); //這裡列印的是第一個節點到其他各點的最短距離資訊
Floy_result_matrix = getMatrix(vexnum, vexnum);
Floy_rount_matrix = getMatrix(vexnum, vexnum);
Floy(graph, Floy_result_matrix, Floy_rount_matrix);
printf("Floy result: \n");
showMatrix(&Floy_result_matrix[0][0], vexnum, vexnum);
showMatrix(&Floy_rount_matrix[0][0], vexnum, vexnum);
destoryMatrix(&Floy_result_matrix, vexnum);
destoryMatrix(&Floy_rount_matrix, vexnum);
destoryGraph(&graph);
free(dij_dist);
return 0;
}
void showMatrix_1(int array[][MAXVEXNUM], int n) {
int i;
int j;
for(i = 0; i < n; i++) {
for(j = 0; j < n; j++) {
// printf("%d ", array[i][j]);
printf("%6d ", *(*(array + i)+j));
}
printf("\n");
}
printf("\n");
}
/*
6 10
1
2
3
4
5
6
0 1 10
0 2 21
0 4 8
1 2 18
1 3 5
1 5 6
2 4 25
2 5 19
3 5 7
4 5 33
*/
自己寫的矩陣輸出 銷燬函式 標頭檔案中有用到
void showArray(int *ArrayFirstAddress, int array_length);
void showMatrix(int *MatrixFisrtAddress, int row, int col) {
int i;
int j;
for(i = 0; i < row; i++) {
for(j = 0; j < col; j++) {
printf("%5d", *(MatrixFisrtAddress++));
}
printf("\n");
}
printf("\n\n");
}
//銷燬陣列
void destoryArray(int **array);
void destoryMatrix(int ***array, int row) {
int i;
for(i = 0; i < row; i++) {
free((*array)[i]);
(*array)[i] = NULL;
}
}