在平时开车的时候,去一个地方总是用的路线规划软件,对于路线的规划中,有行驶路程最短的,有经过收费站最少的。根据不同的需求,对于路径长度的度量做出改变,以经过收费站数目作为度量(图中将收费站表示为顶点),就是最简单的最短路径问题,用图的广度优先搜索就可以实现。而为了实际表达现实交通注重的行驶路程的代价或时间,常用有向连通网表示,习惯将路径上的第一个顶点称为源点,最后一个称为终点。
常见的最短路径问题:
- 从某个源点到其余顶点的最短路径问题
给定带权有向图G和源点v0, 来求从v0到其余各个顶点的最短路径。Dijkstra提出的是一个按路径长度递增的次序产生最短路的算法。 - 每一对顶点之间的最短路径问题
求解每一对顶点之间的最短路径问题有两种方法:
①分别以图中的每一个顶点共调用n次Dijstra 算法
②是Folyd算法
但是两种算法的时间负责度均为O(nnn),但Folyd形式上相对比较简单。
两个算法的实现建立在图的存储结构为邻接矩阵的基础上,相关知识可参考本专栏内容—— 数据结构_图.
Dijkstra 算法
算法思想
Dijkstra算法的求解过程
- 对于有向网N=(V, E),将N中的顶点分成两组:
第一组S:已求出的最短路径的终点集合(初始时只包含源点v0)。
第二组V−S:尚未求出的最短路径的顶点集合(初始时为V−{v0})。- 按各顶点与v0间最短路径长度递增的次序,逐个将集合V−S中的顶点加入到集合S中去。在这个过程中,总保持从v0到集合S中各顶点的路径长度始终不大于到集合V−S中各顶点的路径长度。——即现在加入的是某一顶点vi∈S,下一个加入的顶点vj∈V-S。因为顶点是按照边的递增选择,所以(v0,vi)一定小于(v0,vj)的路径长度。((v0,vi)表示的是v0到vi的最短路径,不一定是弧<v0,vi>的路径长度,可能存在一个中继顶点使得{v0,vk,vi}这样一条路径小于并更新(v0,vi)的路径长度,但是不管更新与否始终小于<v0,vj>的路径长度)
代码实现
Dijkstra算法的实现
- 使用图的邻接矩阵E表示有向带权连通网G,E[i][j]表示弧<vi, vj>上的权值。若<vi, vj>不存在,则置E[i][j]为∞,源点为v0。
- 借助的数据结构
①一维数组S[i]:记录从源点v0到终点vi是否已被确定最短路径长度,true表示确定,false表示尚未确定。(由于选择边是选择最小边,对于已经加入S集合的顶点记录需要使用数组)
② 一维数组Path[i]:记录从源点v0到终点vi的当前最短路径上vi的直接前驱顶点序号(即若存在中继顶点,值为中继顶点)。其初值为:如果从v0到vi有弧,则Path [i]为v0;否则为−1。(如果存在一条顶点序号为{1,4,6}的最短路径,那么Path[6] = 4, Path[4] = 1,Path[1] = -1)
③ 一维数组D[i]:记录从源点v0到终点vi的当前最短路径长度。其初值为:如果从v0到vi有弧,则D[i]为弧上的权值;否则为∞。- 实现步骤:
①选择路径长度最短的路径(v0, vk),并将vk加入到顶点集S中。
②每加入新的顶点到顶点集S,对V-S顶点来说,可能存在这个顶点作为中继顶点,来更新路径。 即原来v0到vi的最短路径长度为 D[ i ],加进vk之后,以vk作为中继顶点,存在D[k]+E[k][i],使D[k]+E[k][i]<D[i],则用D[k]+E[k][i]值取代D[i]。
③重复①
/// ===============================================
/// Dijstra算法实现
/// ===============================================
bool S[MAX]; //记录顶点的访问情况,已访问的置为ture
int Path[MAX]; //记录源点到该顶点路径中的尾顶点的上一个顶点序号
int D[MAX]; //记录单源最短路径的路径长度
void Dijstra(G_Amatreix g,int v) {
for (int i = 0; i < g.n; ++i) { //用源点将各个辅助数据结构初始化
S[i] = false;
D[i] = g.E[v][i];
if (D[i] < Maxint)
Path[i] = v;
else
Path[v] = -1;
}
S[v] = true; //将源点的辅助数据结构置数
D[v] = 0;
for (int i = 1; i < g.n; ++i) { //依次扫描下n-1个顶点
int min = Maxint;
for (int j = 0; j < g.n; ++j) { //找出最小权值边加入顶点集合 S[]
if (D[j] < min && !S[j]) {
v = j;
min = D[j];
}
}
S[v] = true;
for (int k = 0; k < g.n; ++k) { //找出可以用该顶点所在路径可以更新剩余顶点的最短路径
if (!S[k] && D[v] + g.E[v][k] < D[k]) {
D[k] = D[v] + g.E[v][k];
Path[k] = v;
}
}
}
}
测试
Folyd 算法
算法思想
Floyd的算法思想
- 辅助的数据结构:
①二维数组Path[i][j]:最短路径上顶点vj的前驱顶点的序号。
②二维数组D[i][j]:记录顶点vi和vj之间的最短路径长度。
将vi到vj的最短路径长度初始化,即D[i][j]=G.arcs[i][j],然后进行n次比较和更新。- 更新实现过程:
(1)
① 在 vi 和 vj 间加入顶点 v0
②比较 {vi, vj} 和 {vi, v0, vj} 的路径长度
③取其中较短者作为 vi 到 vj 的中间顶点序号不大于0的最短路径。
(2)
①再在 vi 和 vj 间加入顶点 v1。而 {vi,…, v1} 和 {v1,…, vj} 是(1)中求出来的中间顶点的序号不大于0的最短路径 ,合并得 {vi,…, v1, …,vj} (若(1)中没有更新就是 {vi, v1,vj} ,反之就是{ {vi, v0,v1,v0,vj}, {vi, v0,v1,vj}, {vi,v1,v0,vj} }中一种)
②比较 {vi,…, v1,…, vj} 与(1)求出的 {vi, (v0) ,vj} 比较 (根据(1)得结果,v0可能是最短路径中得中间顶点,也可能不是)
③取其中较短者作为vi到vj的中间顶点序号不大于1的最短路径。
(3)
① 依次类推,在 vi 和 vj 间加入顶点 vk 。而 {vi,…, vk} 和 {vk,…, vj} 分别是从 vi 到 vk 和从 vk 到 vj 的中间顶点的序号不大于k−1的最短路径
②比较(vi,…, vk,…, vj)和上一步得到的从vi到vj且中间顶点序号不大于k−1的最短路径相比较
③其长度较短者便是从vi到vj的中间顶点的序号不大于k的最短路径。
(4)
这样,经过n次比较后,得是从vi到vj的最短路径,其路径值存储于D[i][j] , 前驱顶点存储于Path[i][j]。- 最后得到得D[ i ][ j ]可以表示为一个n阶方阵D
D(−1)[ i ][ j ] = G.arcs[ i ][ j ]
D(k)[ i ][ j ] = Min{ D(k−1)[ i ][ j ],D(k−1)[ i ][ k ] + D(k−1)[ k ][ j ] } 0≤k≤n−1
D(k)[i][j]是从vi到vj的中间顶点的序号不大于k(也就是加入中间顶点vk)的最短路径的长度
代码实现
主要函数
/// ===============================================
/// Folyd算法实现
/// ===============================================
int Path1[MAX][MAX]; //记录源点到该顶点路径中的尾顶点的上一个顶点序号
int D1[MAX][MAX]; //记录最短路径的路径长度
void Floyd(G_Amatreix g) {
for (int i = 0; i < g.n; ++i) //用邻接矩阵将各个辅助数据结构初始化
for (int j = 0; j < g.n; ++j) {
D1[i][j] = g.E[i][j];
if (D1[i][j] < Maxint)
Path1[i][j] = i;
else
Path1[i][j] = -1;
}
for (int k = 0; k < g.n; ++k) //依次使用中间顶点对于最短路径的更新
for (int i = 0; i < g.n; ++i)
for (int j = 0; j < g.n; ++j) {
if ((D1[i][k] + D1[k][j] < D1[i][j]) && (i!=j)) {
D1[i][j] = D1[i][k] + D1[k][j];
Path1[i][j] = Path1[k][j];
}
}
}
测试
完整代码
所需头文件
///GAmatrix.h
#pragma once
#include<stdio.h>
#define Maxint 32767
#ifndef MAX
#define MAX 20
#endif // MAX
typedef char VertexType;
typedef int Edgetype;
typedef struct {
VertexType V[MAX];
Edgetype E[MAX][MAX];
int n, e;
}G_Amatreix;
//创建图
void CreateG(G_Amatreix& g);
//找到顶点在顶点集中的标号
int LocateV(G_Amatreix g, VertexType v);
测试c文件
#include<stdio.h>
#include"GAmatrix.h"
/// ===============================================
/// 图邻接矩阵接口实现
/// ===============================================
//创建图
void CreateG(G_Amatreix& g) {
printf("input n , e:\n");
scanf_s("%d,%d", &g.n, &g.e);
getchar();
//初始化V
for (int i = 0; i < g.n; ++i) {
printf("input V[%d]:", i);
scanf_s("%c", &g.V[i], 1);
char ch = getchar();
while (ch != '\n')
ch = getchar();
}
//初始化E
for (int i = 0; i < g.n; ++i) {
for (int j = 0; j < g.n; ++j) {
g.E[i][j] = Maxint; //创立网是为无穷大,图时为0
}
}
//输入边
char v1 = 0, v2 = 0;
int m = 0;;
int j, k;
for (int i = 0; i < g.e; ++i) {
printf("input the edge:(v1 v2 w)\n");
scanf_s("%c %c %d", &v1, 1, &v2, 1, &m);
char ch = getchar();
while (ch != '\n')
ch = getchar();
j = LocateV(g, v1);
k = LocateV(g, v2);
g.E[j][k] = m; //创立网时为权值w,图时为1
//g.E[k][j]=g.E[j][k]; //无向时为对称矩阵
}
输出边
//for (int i = 0; i < g.n; ++i) {
// printf("顶点%c:\n", g.V[i]);
// for (int j = 0; j < g.n; ++j) {
// if (g.E[i][j] != Maxint)
// printf("边为<%c,%c>,权值为%d:\n", g.V[i], g.V[j], g.E[i][j]);
// }
//}
}
//找到顶点在顶点集中的标号
int LocateV(G_Amatreix g, VertexType v) {
int j = 0;
for (; j < g.n; ++j) {
if (g.V[j] == v)
return j;
}
return j;
}
/// ===============================================
/// Dijstra算法实现
/// ===============================================
bool S[MAX]; //记录顶点的访问情况,已访问的置为ture
int Path[MAX]; //记录源点到该顶点路径中的尾顶点的上一个顶点序号
int D[MAX]; //记录单源最短路径的路径长度
void Dijstra(G_Amatreix g,int v) {
for (int i = 0; i < g.n; ++i) { //用源点将各个辅助数据结构初始化
S[i] = false;
D[i] = g.E[v][i];
if (D[i] < Maxint)
Path[i] = v;
else
Path[v] = -1;
}
S[v] = true; //将源点的辅助数据结构置数
D[v] = 0;
for (int i = 1; i < g.n; ++i) { //依次扫描下n-1个顶点
int min = Maxint;
for (int j = 0; j < g.n; ++j) { //找出最小权值边加入顶点集合 S[]
if (D[j] < min && !S[j]) {
v = j;
min = D[j];
}
}
S[v] = true;
for (int k = 0; k < g.n; ++k) { //找出可以用该顶点所在路径可以更新剩余顶点的最短路径
if (!S[k] && D[v] + g.E[v][k] < D[k]) {
D[k] = D[v] + g.E[v][k];
Path[k] = v;
}
}
}
}
void Print(G_Amatreix g) {
for (int i = 0; i < g.n; ++i) {
printf("S[%d]:%d\tPath[%d]:%d\tD[%d]:%d\n",i,S[i],i,Path[i],i,D[i]);
}
}
/// ===============================================
/// Folyd算法实现
/// ===============================================
int Path1[MAX][MAX]; //记录源点到该顶点路径中的尾顶点的上一个顶点序号
int D1[MAX][MAX]; //记录最短路径的路径长度
void Floyd(G_Amatreix g) {
for (int i = 0; i < g.n; ++i) //用邻接矩阵将各个辅助数据结构初始化
for (int j = 0; j < g.n; ++j) {
D1[i][j] = g.E[i][j];
if (D1[i][j] < Maxint)
Path1[i][j] = i;
else
Path1[i][j] = -1;
}
for (int k = 0; k < g.n; ++k) //依次使用中间顶点对于最短路径的更新
for (int i = 0; i < g.n; ++i)
for (int j = 0; j < g.n; ++j) {
if ((D1[i][k] + D1[k][j] < D1[i][j]) && (i!=j)) {
D1[i][j] = D1[i][k] + D1[k][j];
Path1[i][j] = Path1[k][j];
}
}
}
void Print1(G_Amatreix g) {
for (int i = 0; i < g.n; ++i)
for (int j = 0; j < g.n; ++j) {
if(D1[i][j]!= Maxint)
printf("D1[%d][%d]:%d\n",i,j,D1[i][j]);
}
}
int main(int argc, char* argv[]) {
G_Amatreix g;
CreateG(g);
printf("\nDijstra:\n");
Dijstra(g, 0);
Print(g);
printf("\nFolyd:\n");
Floyd(g);
Print1(g);
return 0;
}