一、最短路径概念
图中一个顶点到另一个顶点可能存在多条路径,每条路径所经过边的数量可能不同,每条路径所经过边的权值也可能不同,我们把花费最小的路径成为两个顶点的最短路径。
最短路径相关的两种常用算法:迪克斯特拉(Dijstra)算法和弗洛伊德(Floyd)算法。Dijstra算法用于快速求得图中一个顶点到其他所有顶点的最短距离和路径,Floyd算法用于求图中每对顶点的最短路径。
二、应用
最短路径问题已经应用在了诸多领域,如地图导航,公路建设,路由器寻址等等。
三、Dijstra算法和Floyd算法的具体实现
(1)Dijstra算法
Dijstra算法的基本思想是:将图中的顶点集合分成两组,第1组为已求出最短路径的顶点集合S,第2组为其余未确定最短路径的顶点集合U。然后从一个已知的顶点k开始,寻找离k最近的顶点imin,然后把顶点imin加入到第1组顶点集合S中,如果以顶点imin作为中间点到其他顶点的距离更短,则设置imin最为路径的中间点,并更新k到其他顶点的最短距离,重复寻找最近顶点imin直至所有顶点都加入到集合S中。
其执行步骤如下:
①将图中的顶点集合分成两组,第1组为已求出最短路径的顶点集合S,第2组为其余未确定最短路径的顶点集合U。
②将已知的起始点k加入S中,并初始化k到其他顶点的最短距离为有向图相关边的权值(若不存在边,设置距离为无穷大)。
③重复步骤④,直至所有顶点都加入到集合S中。
④寻找离k最近的顶点imin,然后把顶点imin加入到第1组顶点集合S中,如果以顶点imin作为中间点到其他顶点的距离更短,则设置imin最为路径的中间点,并更新k到其他顶点的最短距离。
C++代码实现:
图的邻接矩阵声明:
#define N 100 typedef char ElemType; /* 图的邻接矩阵声明 */ typedef struct _MGraph { int edges[N][N]; //边集合 int n; //顶点数 }MGraph;
Dijstra算法:
/* 迪克斯特拉算法 g存储有向图的边 k代表出发的顶点 path[i]保存第i个顶点的上一个顶点 dis[i]保存从k出发到顶点i的最短距离 */ void Dijkstra(MGraph &g, int k, int path[], int dis[]) { int* visited = new int[g.n](); //存储顶点是否被访问过,初始化为0 for (int i = 0; i < g.n; i++) { dis[i] = g.edges[k][i]; //初始化最短距离数组 path[i] = k; //初始化路径数组 } visited[k] = 1; dis[k] = 0; for (int cnt = 1; cnt < g.n; cnt++) //循环n-1次 { int imin = -1; //存储最短边的下标 for (int i = 0; i < g.n; i++) //寻找没访问过的最短边 { if (!visited[i] && (imin == -1 || dis[i] < dis[imin])) imin = i; } visited[imin] = 1; for (int i = 0; i < g.n; i++) //如果新的顶点到其他顶点的距离更短,更新最短距离和路径 { if (!visited[i] && dis[imin] + g.edges[imin][i] < dis[i]) { dis[i] = dis[imin] + g.edges[imin][i]; path[i] = imin; } } } delete[] visited; //记得释放内存 }
根据Dijstra算法生成的path数组输出路径:
/* 输出从开始顶点到顶点k的最短路径 */ void DisplayPath(int k, int path[]) { stack<int> s; while (path[k] != k) { s.push(k); k = path[k]; } s.push(k); int cnt = 0; while (!s.empty()) { if (cnt++ > 0) cout << "->"; cout << s.top(); s.pop(); } cout << endl; }
Dijstra算法包含了两重for循环,其时间复杂度为O(n²)。
(2)Floyd算法
Floyd算法的基本思想是:用一个二维数组dis来保存每对顶点之间的最短路径长度,即dis[i][j]表示从顶点i到顶点j的最短路径长度,dis数组初始化为图的邻接矩阵数组dis[i][j] = g.edges[i][j]。从顶点k=0开始,将k作为中间节点,如果顶点i以k最为中间节点到达顶点j的距离更短,则设置k为中间节点,并更新i到j的最短路径。将k的值加1,重复选取中间节点直到所有顶点都被假设为中间节点为止。
其执行步骤如下:
①用一个二维数组dis来保存每对顶点之间的最短路径长度,即dis[i][j]表示从顶点i到顶点j的最短路径长度,dis数组初始化为图的邻接矩阵数组dis[i][j] = g.edges[i][j]。
②从顶点k=0开始,将k假设为中间节点,重复步骤③直至所有顶点都被假设为中间节点为止。
③如果顶点i以k最为中间节点到达顶点j的距离更短,则设置k为中间节点,并更新i到j的最短路径。
C++代码实现:
Floyd算法:
/* 弗洛伊德算法 g存储有向图的边 dis[i][j]存储顶点i到顶点j的最短距离长度 path[i][j]存储顶点j的上一个顶点 */ void Floyd(MGraph& g, int dis[][N], int path[][N]) { for (int i = 0; i < g.n; i++) { for (int j = 0; j < g.n; j++) { dis[i][j] = (i == j ? 0 : g.edges[i][j]); //初始化距离数组 path[i][j] = i; //初始化路径数组 } } 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 (dis[i][k] + dis[k][j] < dis[i][j]) //如果以K为中间点距离更短,更新距离数组和路径数组 { dis[i][j] = dis[i][k] + dis[k][j]; path[i][j] = path[k][j]; } } } } }
根据Floyd生成的path数组输出最短路径:
/* 输出每对顶点的最短路径 */ void DisplayPath(int n, int path[][N], int dis[][N]) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { cout << i << "-" << j << "最短路径长度:" << dis[i][j] << " 最短路径:"; DisplayPath(j, path[i]); } } }
Floyd算法包含三重for循环,其时间复杂度为O(n³),相当于以每个顶点作为源点调用Dijstra算法的时间复杂度,但Floyd算法代码实现简单。
四、测试
问题:
输入一个有向图,求顶点0到其他顶点的最短路径长度和最短路径,以及每对顶点的最短路径长度和最短路径。
样例输入:
4 8
0 1 5
0 3 7
1 2 4
1 3 2
2 0 3
2 1 3
2 3 2
3 2 1
样例输出:
0-0最短路径长度:0 最短路径:0
0-1最短路径长度:5 最短路径:0->1
0-2最短路径长度:8 最短路径:0->3->2
0-3最短路径长度:7 最短路径:0->3
0-0最短路径长度:0 最短路径:0
0-1最短路径长度:5 最短路径:0->1
0-2最短路径长度:8 最短路径:0->3->2
0-3最短路径长度:7 最短路径:0->3
1-0最短路径长度:6 最短路径:1->3->2->0
1-1最短路径长度:0 最短路径:1
1-2最短路径长度:3 最短路径:1->3->2
1-3最短路径长度:2 最短路径:1->3
2-0最短路径长度:3 最短路径:2->0
2-1最短路径长度:3 最短路径:2->1
2-2最短路径长度:0 最短路径:2
2-3最短路径长度:2 最短路径:2->3
3-0最短路径长度:4 最短路径:3->2->0
3-1最短路径长度:4 最短路径:3->2->1
3-2最短路径长度:1 最短路径:3->2
3-3最短路径长度:0 最短路径:3
#include <iostream> #include <stack> using namespace std; #define N 100 typedef char ElemType; /* 图的邻接矩阵声明 */ typedef struct _MGraph { int edges[N][N]; //边集合 int n; //顶点数 }MGraph; /* 迪克斯特拉算法 g存储有向图的边 k代表出发的顶点 path[i]保存第i个顶点的上一个顶点 dis[i]保存从k出发到顶点i的最短距离 */ void Dijkstra(MGraph &g, int k, int path[], int dis[]) { int* visited = new int[g.n](); //存储顶点是否被访问过,初始化为0 for (int i = 0; i < g.n; i++) { dis[i] = g.edges[k][i]; //初始化最短距离数组 path[i] = k; //初始化路径数组 } visited[k] = 1; dis[k] = 0; for (int cnt = 1; cnt < g.n; cnt++) //循环n-1次 { int imin = -1; //存储最短边的下标 for (int i = 0; i < g.n; i++) //寻找没访问过的最短边 { if (!visited[i] && (imin == -1 || dis[i] < dis[imin])) imin = i; } visited[imin] = 1; for (int i = 0; i < g.n; i++) //如果新的顶点到其他顶点的距离更短,更新最短距离和路径 { if (!visited[i] && dis[imin] + g.edges[imin][i] < dis[i]) { dis[i] = dis[imin] + g.edges[imin][i]; path[i] = imin; } } } delete[] visited; //记得释放内存 } /* 输出从开始顶点到顶点k的最短路径 */ void DisplayPath(int k, int path[]) { stack<int> s; while (path[k] != k) { s.push(k); k = path[k]; } s.push(k); int cnt = 0; while (!s.empty()) { if(cnt++ > 0) cout << "->"; cout << s.top(); s.pop(); } cout << endl; } /* 弗洛伊德算法 g存储有向图的边 dis[i][j]存储顶点i到顶点j的最短距离长度 path[i][j]存储顶点j的上一个顶点 */ void Floyd(MGraph& g, int dis[][N], int path[][N]) { for (int i = 0; i < g.n; i++) { for (int j = 0; j < g.n; j++) { dis[i][j] = (i == j ? 0 : g.edges[i][j]); //初始化距离数组 path[i][j] = i; //初始化路径数组 } } 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 (dis[i][k] + dis[k][j] < dis[i][j]) //如果以K为中间点距离更短,更新距离数组和路径数组 { dis[i][j] = dis[i][k] + dis[k][j]; path[i][j] = path[k][j]; } } } } } /* 输出每对顶点的最短路径 */ void DisplayPath(int n, int path[][N], int dis[][N]) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { cout << i << "-" << j << "最短路径长度:" << dis[i][j] << " 最短路径:"; DisplayPath(j, path[i]); } } } int main() { MGraph g; while (cin >> g.n) { for (int i = 0; i < g.n; i++) for (int j = 0; j < g.n; j++) g.edges[i][j] = INT16_MAX; int m, u, v, w; cin >> m; while (m-- > 0) { cin >> u >> v >> w; g.edges[u][v] = w; } //Dijkstra { int* path = new int[g.n]; int* dis = new int[g.n]; Dijkstra(g, 0, path, dis); for (int i = 0; i < g.n; i++) { cout << 0 << "-" << i << "最短路径长度:" << dis[i] << " 最短路径:"; DisplayPath(i, path); } delete[] path, dis; } //Floyd { int path[N][N], dis[N][N]; Floyd(g, dis, path); DisplayPath(g.n, path, dis); } } return 0; }
参考文献
[1] 李春葆.数据结构教程.清华大学出版社,2013.