前言
本文讲述如何求得图中的最短路径,主要讲解两个算法,Dijkstra算法和Floyd算法
举个例子
下图中从V0出发到达V8,求出权值和最小的的路径
Dijkstra算法
辅助数组
使用三个数组来辅助计算过程中的值,
- final数组:标示从源点(V0)到任意一个顶点是否已经求得了最短路径。已经求得用1标示,没有求得用0
- D数组:标示源点(V0)可以到达的某个顶点的路径。下标是顶点,值是源点到达某顶点的路径权值和。
- P数组:到达当前顶点的路径前驱顶点下标。
代码
准备代码:类型与图的创建
#define MAXVERTEX 10
#define INVALID_VALUE 65535
//P数组类型
typedef int ArcPath[MAXVERTEX];
//D数组类型
typedef int ShortPathTable[MAXVERTEX];
//图结构体
typedef struct Graph {
int vex[MAXVERTEX];
int arc[MAXVERTEX][MAXVERTEX];
int numVex;
int numEdge;
}MGraph;
//创建图
void CreateGraph(MGraph *G) {
G->numVex = 9;
G->numEdge = 16;
for (int i = 0; i < G->numVex; i++) {
G->vex[i] = i;
for (int j = 0; j < G->numVex; j++) {
G->arc[i][j] = (i == j)? 0: INVALID_VALUE;
}
}
G->arc[0][1] = 1;
G->arc[0][2] = 5;
G->arc[1][2] = 3;
G->arc[1][3] = 7;
G->arc[1][4] = 5;
G->arc[2][4] = 1;
G->arc[2][5] = 7;
G->arc[3][4] = 2;
G->arc[3][6] = 3;
G->arc[4][5] = 3;
G->arc[4][6] = 6;
G->arc[4][7] = 9;
G->arc[5][7] = 5;
G->arc[6][7] = 2;
G->arc[6][8] = 7;
G->arc[7][8] = 4;
for (int i = 0; i < G->numVex; i++) {
for (int j = 0; j < G->numVex; j++) {
G->arc[j][i] = G->arc[i][j];
printf("%d ", G->arc[i][j]);
}
printf("\n");
}
}
算法
//Dijkstra算法,参数:图、开始点、P数组、D数组
void ShortestPath_Dijkstra(MGraph g, int start, ArcPath *P, ShortPathTable *D) {
//定义final数组
int final[MAXVERTEX] = {0};
//初始化开始点
int k = start;
//初始化三个数组数据
final[k] = 1;//final中标记开始点已经加入
(*P)[k] = -1;//开始点的前驱结点为-1
//将开始点的邻接矩阵中的值赋值给D,也就是将与开始点有关联的顶点记录到D中
for (int i = 0; i < g.numVex; i++) {
(*D)[i] = g.arc[k][i];
}
for (int i = 0; i < g.numVex; i++) {
//获取D数组中的最小路径的位置m和权值min
int min = INVALID_VALUE;
int m = 0;
for (int j = 0; j < g.numVex; j++) {
if (final[j] == 0 && (*D)[j] < min) {
m = j;
min = (*D)[j];
}
}
//更新final数组
final[m] = 1;
//将找到的最小路径的位置对应的邻接矩阵中的顶点信息,加入到D数组中,并更新P数组
for (int k = 0; k < g.numVex; k++) {
//判断:没有记录在final数组中 && D中的值大于邻接矩阵中的值,更新D数组:获取更显的路径权值,更新P数组记录前驱顶点的位置
if (final[k] == 0 && g.arc[m][k] + min < (*D)[k]) {
(*D)[k] = g.arc[m][k] + min;
(*P)[k] = m;
}
}
}
printf("final:");
for (int i = 0; i < g.numVex; i++) {
printf("%d ", final[i]);
}
printf("\n");
printf("D:");
for (int i = 0; i < g.numVex; i++) {
printf("%d ", (*D)[i]);
}
printf("\n");
printf("P:");
for (int i = 0; i < g.numVex; i++) {
printf("%d ", (*P)[i]);
}
printf("\n");
}
运行
int main(int argc, const char * argv[]) {
printf("Hello, Dijkstra!\n");
MGraph g;
CreateGraph(&g);
ArcPath P = {0};
ShortPathTable D = {INVALID_VALUE};
int start = 0;
ShortestPath_Dijkstra(g, start, &P, &D);
printf("最短路径路线:\n");
int j;
for(int i=1;i<g.numVex;++i)
{
printf("v%d -> v%d : ",start,i);
j=i;
while(P[j]!=-1)
{
printf("%d ",P[j]);
j=P[j];
}
printf("\n");
}
printf("\n最短路径权值和\n");
for(int i=1;i<g.numVex;++i)
printf("v%d -> v%d : %d \n",g.vex[0],g.vex[i],D[i]);
printf("\n");
return 0;
}
Floyd算法
与Dijkstra的区别
上面学到的Dijkstra算法,是以源点为出发点,算出了到达终点路径中经过的所有顶点的路径值。而Floyd算法更加全面,将任意两个点的路径都会记录算出。所以说Floyd算法,需要二维数组的辅助。
二维数组-辅助
需要两个二位数辅助
- D数组:初始获取邻接矩阵中的所有内容
- P数组:同样也是个二维数组,记录前驱结点
简单示例以及公式
用一个简单的图结构解释一下Floyd算法是如何操作的
邻接矩阵
| V0 | V1 | V2 | |
|---|---|---|---|
| V0 | 0 | 2 | 1 |
| V1 | 2 | 0 | 5 |
| V2 | 1 | 5 | 0 |
初始化D数组,将邻接矩阵赋值给D数组。
- 计算V1-V2的路径,通过D数组中的记录D[1][2]=5
- 计算V1-V0-V2的路径,通过D数组中的记录D[1][0]=2,D[0][2]=1,两个路径和2+1=3
- V1-V2路径 大于 V1-V0-V2路径,更新D数组,参考表格中的2+1=3
| V0 | V1 | V2 | |
|---|---|---|---|
| V0 | 0 | 2 | 1 |
| V1 | 2 | 0 | 2+1=3 |
| V2 | 1 | 2+1=3 | 0 |
同理P二维数组也就行相关位置修改,如图
公式
代码
准备代码:类型与图的创建
//图结构体
typedef struct Graph{
int vex[MAXVERTEX];
int arc[MAXVERTEX][MAXVERTEX];
int numVex;
int numEdge;
}MGraph;
//P数组类型
typedef int ArcPath[MAXVERTEX][MAXVERTEX];
//D数组类型
typedef int ShortPathTable[MAXVERTEX][MAXVERTEX];
void CreateGraph(MGraph *G) {
G->numVex = 9;
G->numEdge = 16;
for (int i = 0; i < G->numVex; i++) {
G->vex[i] = i;
for (int j = 0; j < G->numVex; j++) {
G->arc[i][j] = (i == j)? 0: INVALID_VALUE;
}
}
G->arc[0][1] = 1;
G->arc[0][2] = 5;
G->arc[1][2] = 3;
G->arc[1][3] = 7;
G->arc[1][4] = 5;
G->arc[2][4] = 1;
G->arc[2][5] = 7;
G->arc[3][4] = 2;
G->arc[3][6] = 3;
G->arc[4][5] = 3;
G->arc[4][6] = 6;
G->arc[4][7] = 9;
G->arc[5][7] = 5;
G->arc[6][7] = 2;
G->arc[6][8] = 7;
G->arc[7][8] = 4;
for (int i = 0; i < G->numVex; i++) {
for (int j = 0; j < G->numVex; j++) {
G->arc[j][i] = G->arc[i][j];
printf("%d ", G->arc[i][j]);
}
printf("\n");
}
}
算法
//算法,参数:图,P二维数组,D二维数组
void ShortestPath_Floyd(MGraph g, ArcPath *P, ShortPathTable *D) {
//初始化两个二维数组
for (int i = 0; i < g.numVex; i++) {
for (int j = 0; j < g.numVex; j++) {
(*D)[i][j] = g.arc[i][j];
(*P)[i][j] = j;
}
}
for (int i = 0; i < g.numVex; i++) {
for (int j = 0; j < g.numVex; j++) {
for (int k = 0; k < g.numVex; k++) {
//公式,更新D和P
if ((*D)[j][k] > (*D)[j][i] + (*D)[i][k]) {
(*D)[j][k] = (*D)[j][i] + (*D)[i][k];
(*P)[j][k] = (*P)[j][i];
}
}
}
}
}
运行
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, Floyd!\n");
int v, w, k;
MGraph g;
CreateGraph(&g);
ArcPath p;
ShortPathTable d;
ShortestPath_Floyd(g, &p, &d);
//打印所有可能的顶点之间的最短路径以及路线值
printf("各顶点间最短路径如下:\n");
for(v=0; v<g.numVex; v++)
{
for(w=v+1; w<g.numVex; w++)
{
printf("v%d-v%d weight: %d ",v,w,d[v][w]);
//获得第一个路径顶点下标
k = p[v][w];
//打印源点
printf(" path: %d",v);
//如果路径顶点下标不是终点
while(k!=w)
{
//打印路径顶点
printf(" -> %d",k);
//获得下一个路径顶点下标
k=p[k][w];
}
//打印终点
printf(" -> %d\n",w);
}
printf("\n");
}
//打印最终变换后的最短路径D数组
printf("最短路径D数组\n");
for(v=0; v<g.numVex; v++)
{
for(w=0; w<g.numVex; w++)
{
printf("%d\t",d[v][w]);
}
printf("\n");
}
//打印最终变换后的最短路径P数组
printf("最短路径P数组\n");
for(v=0; v<g.numVex; v++)
{
for(w=0; w<g.numVex; w++)
{
printf("%d ",p[v][w]);
}
printf("\n");
}
return 0;
}
Hello, Floyd!
0 1 5 65535 65535 65535 65535 65535 65535
1 0 3 7 5 65535 65535 65535 65535
5 3 0 65535 1 7 65535 65535 65535
65535 7 65535 0 2 65535 3 65535 65535
65535 5 1 2 0 3 6 9 65535
65535 65535 7 65535 3 0 65535 5 65535
65535 65535 65535 3 6 65535 0 2 7
65535 65535 65535 65535 9 5 2 0 4
65535 65535 65535 65535 65535 65535 7 4 0
各顶点间最短路径如下:
v0-v1 weight: 1 path: 0 -> 1
v0-v2 weight: 4 path: 0 -> 1 -> 2
v0-v3 weight: 7 path: 0 -> 1 -> 2 -> 4 -> 3
v0-v4 weight: 5 path: 0 -> 1 -> 2 -> 4
v0-v5 weight: 8 path: 0 -> 1 -> 2 -> 4 -> 5
v0-v6 weight: 10 path: 0 -> 1 -> 2 -> 4 -> 3 -> 6
v0-v7 weight: 12 path: 0 -> 1 -> 2 -> 4 -> 3 -> 6 -> 7
v0-v8 weight: 16 path: 0 -> 1 -> 2 -> 4 -> 3 -> 6 -> 7 -> 8
v1-v2 weight: 3 path: 1 -> 2
v1-v3 weight: 6 path: 1 -> 2 -> 4 -> 3
v1-v4 weight: 4 path: 1 -> 2 -> 4
v1-v5 weight: 7 path: 1 -> 2 -> 4 -> 5
v1-v6 weight: 9 path: 1 -> 2 -> 4 -> 3 -> 6
v1-v7 weight: 11 path: 1 -> 2 -> 4 -> 3 -> 6 -> 7
v1-v8 weight: 15 path: 1 -> 2 -> 4 -> 3 -> 6 -> 7 -> 8
v2-v3 weight: 3 path: 2 -> 4 -> 3
v2-v4 weight: 1 path: 2 -> 4
v2-v5 weight: 4 path: 2 -> 4 -> 5
v2-v6 weight: 6 path: 2 -> 4 -> 3 -> 6
v2-v7 weight: 8 path: 2 -> 4 -> 3 -> 6 -> 7
v2-v8 weight: 12 path: 2 -> 4 -> 3 -> 6 -> 7 -> 8
v3-v4 weight: 2 path: 3 -> 4
v3-v5 weight: 5 path: 3 -> 4 -> 5
v3-v6 weight: 3 path: 3 -> 6
v3-v7 weight: 5 path: 3 -> 6 -> 7
v3-v8 weight: 9 path: 3 -> 6 -> 7 -> 8
v4-v5 weight: 3 path: 4 -> 5
v4-v6 weight: 5 path: 4 -> 3 -> 6
v4-v7 weight: 7 path: 4 -> 3 -> 6 -> 7
v4-v8 weight: 11 path: 4 -> 3 -> 6 -> 7 -> 8
v5-v6 weight: 7 path: 5 -> 7 -> 6
v5-v7 weight: 5 path: 5 -> 7
v5-v8 weight: 9 path: 5 -> 7 -> 8
v6-v7 weight: 2 path: 6 -> 7
v6-v8 weight: 6 path: 6 -> 7 -> 8
v7-v8 weight: 4 path: 7 -> 8
最短路径D数组
0 1 4 7 5 8 10 12 16
1 0 3 6 4 7 9 11 15
4 3 0 3 1 4 6 8 12
7 6 3 0 2 5 3 5 9
5 4 1 2 0 3 5 7 11
8 7 4 5 3 0 7 5 9
10 9 6 3 5 7 0 2 6
12 11 8 5 7 5 2 0 4
16 15 12 9 11 9 6 4 0
最短路径P数组
0 1 1 1 1 1 1 1 1
0 1 2 2 2 2 2 2 2
1 1 2 4 4 4 4 4 4
4 4 4 3 4 4 6 6 6
2 2 2 3 4 5 3 3 3
4 4 4 4 4 5 7 7 7
3 3 3 3 3 7 6 7 7
6 6 6 6 6 5 6 7 8
7 7 7 7 7 7 7 7 8
Program ended with exit code: 0