一、最短路径概念
图中一个顶点到另一个顶点可能存在多条路径,每条路径所经过边的数量可能不同,每条路径所经过边的权值也可能不同,我们把花费最小的路径成为两个顶点的最短路径。
最短路径相关的两种常用算法:迪克斯特拉(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 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
#include
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 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.