一、简介
在非网图中,最短路径是指两顶点之间经历的边数最少的路径。在网图中,最短路径是指两顶点之间经历的边上权值之和最少的路径。
路径上的第一个顶点称为源点,最后一个顶点称为终点。
最短路径问题是图的一个比较典型的应用问题。例如,给定某公路网的n个城市以及这些城市之间相通公路的距离,能否找到城市A到城市B之间一条距离最近的通路呢?如果将城市用顶点表示城市间的公路用边表示,公路的长度作为边的权值,这个问题就归结为在网图中求顶点A和顶点B的最短路径。
最短路径问题常见的两种算法是Dijkstra算法和Floyd算法。
二、Dijkstra算法
Dijkstra算法是一种用于求解单源最短路径的经典算法。它可以在带权重的、有向或无向的图中找出从起始点到所有其他节点的最短路径。该算法要求边的权重为非负数,因此不能处理带有负权重的图。
其基本思想是通过贪心策略,每次选择未处理节点中距离起始点最近的节点,逐步找到最短路径。
图采用邻接矩阵存储。
Dijkstra算法主要包括以下步骤:
-
初始化:
- 创建一个数组
dist
,用来记录从起点到各个节点的最短距离。初始时,起点到自身的距离为0,其它节点的距离设为正无穷大。 - 创建一个bool数组
visited
,用来记录节点是否已经被处理过。初始时,所有节点都未被处理过。
- 创建一个数组
-
循环查找:
- 每次从未被处理过的节点中选出距离起点最近的节点
u
,并标记为已处理。 - 遍历所有与
u
相邻的节点v
,如果通过u
到达v
的路径更短,则更新dist[v]
,并将u
作为v
的前驱节点。
- 每次从未被处理过的节点中选出距离起点最近的节点
-
重复:
- 重复以上步骤,直到所有节点都被处理或找到的最短路径已经不能再更新。
-
终止:
- 所有节点被标记为已处理,或者没有可以继续处理的节点时,算法终止。此时,数组
dist
中存放的即是起点到各节点的最短距离。
- 所有节点被标记为已处理,或者没有可以继续处理的节点时,算法终止。此时,数组
#include <iostream>
using namespace std;
#include <vector>
//功能:通过Kijkstra算法求最短路径
//参数:输入起点src、顶点数vertex_num、邻接矩阵edge
vector<int> kijkstra(int src, int vertexNum, vector<vector<int>> edge)
{
vector<int> dist(vertexNum, INT_MAX);
//存放起点到各顶点的最近距离,初始为最大值INT_MAX
vector<bool> visited(vertexNum, false);
//存放各顶点的访问状态,初始为未访问false
for (int i = 0; i < vertexNum; i++) //更新起点到各顶点的距离
{
dist[i] = edge[src][i];
}
dist[src] = 0; //起点到起点的距离为0
visited[src] = true; //将起点标记为已访问
//遍历所有节点,更新起点到各节点的最小距离(更新dist[]数组),最多执行(顶点数-1)次即可,实际上(顶点数-2)应该够了
for (int j = 0; j < vertexNum-1; j++)
{
int min(INT_MAX), u(-1); //存储未访问节点中距离起点最近的点的值和下标
for (int k = 0; k < vertexNum; k++) //遍历所有节点
{
if (!visited[k] && dist[k] < min) //若该节点未被访问且起点到该点的距离较近
{
min = dist[k];
u = k; //u为未访问的点中距离起点最近的点
}
}
if (u == -1) //若没有未访问的点或者没有连通的点
{
return dist; //返回结果,包含起点到各点的最短距离
}
visited[u] = true; //将最近的这个顶点标记为已访问
for (int n = 0; n < vertexNum; n++) //更新起点到各顶点的距离,使起点到各顶点的距离最近
{
if (dist[n] > dist[u] + edge[u][n] && !visited[n])
{ //若从起点到u再到n的距离比直接从起点到n的距离还小
dist[n] = dist[u] + edge[u][n];
}
}
}
return dist; //返回结果,包含起点到各点的最短距离
}
int main()
{
//设置起点为0,顶点数为5
int src(0),dest(4), vertex_num(5);
//图的邻接矩阵,INT_MAX表示两点之间无边,0表示同一个点之间的距离
vector<vector<int>> edge = {{0 , 10 , 3 , INT_MAX, INT_MAX},
{INT_MAX, 0 , 1 , 2 , INT_MAX},
{INT_MAX, 4 , 0 , 8 , 2 },
{INT_MAX, INT_MAX, INT_MAX, 0 , 7 },
{INT_MAX, INT_MAX, INT_MAX, 9 , 0 }};
//存放起点到各点的最短距离
vector<int> dist;
//调用Kijkstra算法求最短路径
dist = kijkstra(src, vertex_num, edge);
//输入起点到终点的最短距离
cout << dist[dest];
return 0;
}
三、Floyd算法
Floyd算法是一种用于求解图中任意两个顶点之间最短路径的算法。它适用于带权有向图和无向图,能够处理负权边(但不支持负权回路)。
Floyd算法的核心思想是利用动态规划的原则,通过对图中每对顶点之间的路径进行更新,逐步找到所有顶点对之间的最短路径。算法使用一个二维数组 dist
,其中 dist[i][j]
表示从顶点 i
到顶点 j
的最短路径的权重。
同Dijkstra算法,Floyd算法适用于图的邻接矩阵存储结构。
以下是Floyd算法的具体步骤:
- 输入:图的顶点数vertexNum 和边的列表(每条边包含起始顶点、结束顶点及权重)。
- 初始化距离矩阵
dist
:- 对于每个顶点
i
,设置dist[i][i] = 0
。 - 对于每条边
(u, v, w)
,设置dist[u][v] = w
。 - 其余的
dist[i][j]
设置为INT_MAX。
- 对于每个顶点
- 动态规划更新:
- 使用三重循环遍历所有顶点组合
(i, j, k)
,进行路径更新。
- 使用三重循环遍历所有顶点组合
- 输出:最短路径矩阵
dist
。
#include <iostream>
using namespace std;
#include <vector>
//输入参数:起点src、终点dest、顶点数vertexNum,邻接矩阵edge
void Floyd(int src,int dest,int vertexNum, vector<vector<int>> edge)
{
vector<vector<int>> dist(vertexNum, vector<int>(vertexNum, INT_MAX));
//最短距离矩阵,存储两点之间的最短距离
for (int i = 0; i < vertexNum; i++)
{
for (int j = 0; j < vertexNum; j++)
{
if (i == j)
{
dist[i][j] = 0; //自己到自己的距离为0
}
else if (edge[i][j] != 0)
{
dist[i][j] = edge[i][j]; //设置边的权重
}
}
}
//Floyd算法
for (int k = 0; k < vertexNum; k++)
{
for (int i = 0; i < vertexNum; i++)
{
for (int j = 0; j < vertexNum; j++)
{
if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX)//判断i到k,k到j是否通
{
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
//看是i直接到j更近,还是i先经过k再到j更近
}
}
}
}
//输出结果
cout << dist[src][dest] << endl;
}
int main()
{
// 顶点数量
int vertex_num = 4;
// 图的邻接矩阵
vector<vector<int>> edge = {{0, 5, 0, 10},
{0, 0, 3, 0 },
{0, 0, 0, 1 },
{0, 0, 0, 0 }};
//调用Floyd算法求最短路径
Floyd(0, 3, vertex_num, edge);
return 0;
}