题解:AcWing 849 Dijkstra求最短路I
【题目来源】
AcWing:849 Dijkstra求最短路 I - AcWing题库
【题目描述】
给定一个\(n\)个点\(m\)条边的有向图,图中可能存在重边和自环,所有边权均为正值。请你求出\(1\)号点到\(n\)号点的最短距离,如果无法从\(1\)号点走到\(n\)号点,则输出\(-1\)。
【输入】
第一行包含整数\(n\)和\(m\)。
接下来\(m\)行每行包含三个整数 \(x,y,z\),表示存在一条从点\(x\)到点\(y\)的有向边,边长为\(z\)。
【输出】
输出一个整数,表示\(1\)号点到\(n\)号点的最短距离。如果路径不存在,则输出\(-1\)。
【输入样例】
3 3
1 2 2
2 3 1
1 3 4
【输出样例】
3
【核心思想】
-
问题分析:给定有向图 \(G=(V,E)\),其中 \(|V|=n\),\(|E|=m\),所有边权均为正值。需要求从起点 \(1\) 到终点 \(n\) 的最短路径长度。这是一个经典的单源最短路径问题,由于边权为正,可以使用 Dijkstra 算法求解。
-
算法选择:
- Dijkstra 算法(朴素版):基于贪心思想,每次选择距离起点最近的未确定节点,使用邻接矩阵存储图
- 距离数组 \(dist[]\):\(dist[i]\) 表示从起点到节点 \(i\) 的当前最短距离估计值
- 访问标记数组 \(st[]\):标记节点是否已确定最短距离
- 邻接矩阵 \(g[][]\):存储图的边权,\(g[i][j]\) 表示边 \(i \to j\) 的权重
-
关键步骤:
- 初始化:
- 读取 \(n\)(节点数)、\(m\)(边数)
- 初始化邻接矩阵 \(g[i][j] = INF\)(无穷大)
- 读入 \(m\) 条边 \((x, y, z)\),处理重边:\(g[x][y] = \min(g[x][y], z)\)
- 初始化距离数组 \(dist[i] = INF\),\(dist[1] = 0\)(起点距离为0)
- 初始化访问标记数组 \(st[i] = false\)
- 主循环(执行 \(n\) 次,每次确定一个节点的最短距离):
- 找最小:在未确定节点中,找到距离起点最近的节点 \(t\)
- 遍历 \(j\) 从 \(1\) 到 \(n\),若 \(!st[j]\) 且(\(t == -1\) 或 \(dist[j] < dist[t]\)),则 \(t = j\)
- 标记确定:\(st[t] = true\)(节点 \(t\) 的最短距离已确定)
- 松弛更新:遍历所有节点 \(j\),更新 \(dist[j] = \min(dist[j], dist[t] + g[t][j])\)
- 找最小:在未确定节点中,找到距离起点最近的节点 \(t\)
- 返回结果:若 \(dist[n] == INF\) 返回 \(-1\)(不可达),否则返回 \(dist[n]\)
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n^2)\),主循环执行 \(n\) 次,每次找最小节点 \(O(n)\),松弛更新 \(O(n)\)
- 空间复杂度:\(O(n^2)\),邻接矩阵存储需要 \(O(n^2)\),距离数组和标记数组各 \(O(n)\)
- 适用于稠密图(\(m \approx n^2\)),稀疏图建议使用堆优化版本 \(O((n+m) \log n)\)
-
Dijkstra 算法的核心思想:
- 贪心策略:每次选择距离起点最近的未确定节点,其最短距离已经确定
- 松弛操作:通过已确定节点更新相邻节点的距离估计值
- 正权约束:算法正确性依赖于所有边权为正,负权边会导致贪心策略失效
- 最优子结构:从起点到任意节点的最短路径的子路径也是最短路径
- 适用于正权图的单源最短路径问题,如地图导航、网络路由等
【解题思路】

【算法标签】
Dijkstra
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 510; // 最大节点数
const int INF = 0x3f3f3f3f; // 无穷大
int n; // 节点数量
int m; // 边数量
int g[N][N]; // 邻接矩阵存储图
int dist[N]; // 从起点到各点的最短距离
bool st[N]; // 标记节点是否已确定最短距离
// Dijkstra算法求单源最短路径
// 返回:起点1到终点n的最短距离,如果不可达返回-1
int dijkstra()
{
// 第一步:初始化距离数组
memset(dist, 0x3f, sizeof(dist)); // 所有距离初始化为无穷大
dist[1] = 0; // 起点到自身的距离为0
// 第二步:循环n次,每次确定一个最短距离节点
for (int i = 0; i < n; i++)
{
// 找到未确定最短距离节点中,距离起点最近的节点t
int t = -1; // 存储当前最小距离节点的编号
for (int j = 1; j <= n; j++)
{
// 如果节点j未确定最短距离,且
// 1) 这是第一个满足条件的节点 (t==-1)
// 2) 或者节点j的距离比当前最小值更小
if (!st[j] && (t == -1 || dist[t] > dist[j]))
{
t = j; // 更新当前最小值节点
}
}
// 标记节点t已确定最短距离
st[t] = true;
// 第三步:用节点t更新其他未确定节点的距离
for (int j = 1; j <= n; j++)
{
// 如果从起点经过t到j的距离比当前记录更短,则更新
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
}
// 第四步:检查终点是否可达
if (dist[n] == INF)
{
return -1; // 不可达
}
return dist[n]; // 返回最短距离
}
int main()
{
// 输入节点数和边数
scanf("%d%d", &n, &m);
// 初始化邻接矩阵
memset(g, 0x3f, sizeof(g));
// 输入m条边
while (m--)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
// 处理重边:如果有重边,只保留权重最小的一条
g[a][b] = min(g[a][b], c);
}
// 执行Dijkstra算法
int t = dijkstra();
// 输出结果
printf("%d\n", t);
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N=500+3, INF=0x3f3f3f3f; //N节点数量规模
int n, m; //n个点m条边
int g[N][N]; //图邻接矩阵
int dist[N]; //源点到i点的距离
bool st[N]; //点i的距离是否确定
void dijkstra(int s) //Dijkstra最短路径算法,s为源点
{
dist[s]=0; //源点 距离为0 不能添加语句:st[s]=true;第一次找到的点就是源点
for (int i=1; i<=n; i++) { //n点书,找从源点到每个点的最短路,第一次就是源点s
int t=-1, mind=INF; //t:距离最短的点编号
for (int j=1; j<=n; j++) { //找到未访问的顶点中d值最小的顶点
if (st[j]==false && (dist[j]<mind)) {
t=j;
mind = dist[j];
}
}
if (t==-1) return; //找不到小于INF的d[u],说明顶点和源点s不连通
st[t] = true; //距离最短的点t。再进行松弛操作的点进行标记,可以移到for后
//松弛操作,检查与t相连的点j,更新dist[j]
for (int j=1; j<=n; j++)
if (st[j]==false && g[t][j]!=INF)
dist[j]=min(dist[j], dist[t]+g[t][j]);
}
return;
}
int main()
{
cin >> n >> m; //n个点m条边
memset(g, 0x3f, sizeof(g)); //初始化数组g[]为正无穷
memset(dist, 0x3f, sizeof(dist));
while (m--) {
int a, b, c;
cin >> a >> b >> c;
g[a][b] = min(g[a][b], c); //重边去最小的边
if (a==b) g[a][b]=0; //自环边为0
}
dijkstra(1);
if (dist[n]>=INF) dist[n]=-1; //说明顶点n和源点s不连通
cout << dist[n] << endl;
return 0;
}
【运行结果】
3 3
1 2 2
2 3 1
1 3 4
3
浙公网安备 33010602011771号