题解: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

【核心思想】

  1. 问题分析:给定有向图 \(G=(V,E)\),其中 \(|V|=n\)\(|E|=m\),所有边权均为正值。需要求从起点 \(1\) 到终点 \(n\) 的最短路径长度。这是一个经典的单源最短路径问题,由于边权为正,可以使用 Dijkstra 算法求解。

  2. 算法选择

    • Dijkstra 算法(朴素版):基于贪心思想,每次选择距离起点最近的未确定节点,使用邻接矩阵存储图
    • 距离数组 \(dist[]\)\(dist[i]\) 表示从起点到节点 \(i\) 的当前最短距离估计值
    • 访问标记数组 \(st[]\):标记节点是否已确定最短距离
    • 邻接矩阵 \(g[][]\):存储图的边权,\(g[i][j]\) 表示边 \(i \to j\) 的权重
  3. 关键步骤

    • 初始化
      • 读取 \(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])\)
    • 返回结果:若 \(dist[n] == INF\) 返回 \(-1\)(不可达),否则返回 \(dist[n]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(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)\)
  5. 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
posted @ 2026-06-27 14:20  团爸讲算法  阅读(7)  评论(0)    收藏  举报