题解:AcWing 850 Dijkstra II-优化

【题目来源】

AcWing:850 Dijkstra求最短路 II - 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 I 相比,数据范围更大(\(m\) 可达 \(1.5 \times 10^5\)),需要使用堆优化版本才能在时间限制内完成。

  2. 算法选择

    • Dijkstra 算法(堆优化版):使用优先队列(小顶堆)维护未确定节点的距离,将找最小节点的时间从 \(O(n)\) 优化到 \(O(\log n)\)
    • 邻接表存储:使用链式前向星存储图,空间复杂度 \(O(n + m)\),适合稀疏图
    • 距离数组 \(dist[]\)\(dist[i]\) 表示从起点到节点 \(i\) 的当前最短距离估计值
    • 访问标记数组 \(st[]\):标记节点是否已确定最短距离(处理堆中重复元素)
  3. 关键步骤

    • 初始化
      • 读取 \(n\)(节点数)、\(m\)(边数)
      • 初始化邻接表头数组 \(h[i] = -1\),边索引 \(idx = 0\)
      • 读入 \(m\) 条边 \((x, y, z)\),使用 add(x, y, z) 添加到邻接表
      • 初始化距离数组 \(dist[i] = INF\)\(dist[1] = 0\)(起点距离为0)
      • 初始化访问标记数组 \(st[i] = false\)
    • 创建优先队列priority_queue<PII, vector<PII>, greater<PII>> heap,存储 \((距离,节点编号)\)
    • 起点入堆heap.push({0, 1})
    • 主循环(当堆非空时循环):
      • 取出堆顶t = heap.top(),获取当前距离最小的节点,然后 heap.pop()
      • 获取节点信息ver = t.second(节点编号),distance = t.first(当前距离)
      • 跳过已确定节点:若 \(st[ver] = true\),说明该节点已处理过,跳过(处理堆中重复元素)
      • 标记确定\(st[ver] = true\)(节点 \(ver\) 的最短距离已确定)
      • 松弛更新:遍历 \(ver\) 的所有邻接边 \((ver \to j,权重w)\)
        • \(dist[j] > distance + w\),则更新 \(dist[j] = distance + w\)
        • \((dist[j], j)\) 加入堆 heap.push({dist[j], j})
    • 返回结果:若 \(dist[n] == INF\) 返回 \(-1\)(不可达),否则返回 \(dist[n]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O((n + m) \log n)\),每个节点入堆出堆各一次 \(O(\log n)\),每条边可能引起一次入堆操作
    • 空间复杂度:\(O(n + m)\),邻接表存储需要 \(O(n + m)\),距离数组、标记数组、堆各 \(O(n)\)
    • 适用于稀疏图(\(m \ll n^2\)),稠密图建议使用朴素版 \(O(n^2)\)
  5. 堆优化 Dijkstra 的核心思想

    • 优先队列优化:使用小顶堆维护未确定节点的距离,将"找最小"操作从 \(O(n)\) 优化到 \(O(\log n)\)
    • 延迟删除策略:不立即删除堆中过期的距离记录,而是在取出时检查是否已确定,若已确定则跳过
    • 邻接表优势:对于稀疏图,邻接表空间效率远高于邻接矩阵,遍历邻接点的时间为 \(O(出度)\)
    • 正权约束:同朴素版,算法正确性依赖于所有边权为正
    • 适用于大规模稀疏图的单源最短路径问题

【解题思路】

【解题思路2】

【算法标签】

Dijkstra

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 150010;  // 最大节点数
typedef pair<int, int> PII;  // 用于优先队列的pair类型:(距离, 节点编号)

int n;      // 节点数量
int m;      // 边数量
int h[N];   // 邻接表头数组
int w[N];   // 边权数组
int e[N];   // 边的终点数组
int ne[N];  // 下一条边数组
int idx;    // 边的索引
int dist[N];  // 从起点到各点的最短距离
bool st[N];   // 标记节点是否已确定最短距离

// 添加有向边 a->b,权重为c
void add(int a, int b, int c)
{
    e[idx] = b;      // 存储边的终点
    w[idx] = c;      // 存储边的权重
    ne[idx] = h[a];  // 新边指向原来的第一条边
    h[a] = idx;      // 更新头指针指向新边
    idx++;           // 边索引增加
}

// 堆优化Dijkstra算法求单源最短路径
// 返回:起点1到终点n的最短距离,如果不可达返回-1
int dijkstra()
{
    // 第一步:初始化距离数组
    memset(dist, 0x3f, sizeof(dist));  // 所有距离初始化为无穷大
    dist[1] = 0;                       // 起点到自身的距离为0

    // 第二步:创建小顶堆优先队列
    // 优先队列按pair的第一个元素(距离)从小到大排序
    priority_queue<PII, vector<PII>, greater<PII>> heap;

    // 将起点加入优先队列
    heap.push({0, 1});  // (距离, 节点编号)

    // 第三步:处理优先队列直到为空
    while (heap.size())
    {
        // 取出堆顶元素(当前距离最小的节点)
        auto t = heap.top();
        heap.pop();

        int ver = t.second;      // 节点编号
        int distance = t.first;   // 当前距离

        // 如果这个节点已经确定最短距离,跳过(这是关键优化)
        if (st[ver])
        {
            continue;
        }

        // 标记节点ver已确定最短距离
        st[ver] = true;

        // 第四步:遍历节点ver的所有邻接边
        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];  // 邻接节点j

            // 如果通过ver到j的距离更短,则更新
            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});  // 将j和新距离加入优先队列
            }
        }
    }

    // 第五步:检查终点是否可达
    if (dist[n] == 0x3f3f3f3f)
    {
        return -1;  // 不可达
    }
    return dist[n];  // 返回最短距离
}

int main()
{
    // 输入节点数和边数
    scanf("%d%d", &n, &m);

    // 初始化邻接表
    memset(h, -1, sizeof(h));

    // 输入m条有向边
    while (m--)
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    // 执行Dijkstra算法
    int t = dijkstra();

    // 输出结果
    printf("%d\n", t);

    return 0;
}
#include <bits/stdc++.h>
using namespace std;

const int N = 150000 + 5, INF = 0x3f3f3f3f;  // 定义节点数量规模和无穷大常量
typedef pair<int, int> Node;  // 定义节点类型,包含源点到该节点的距离和节点编号
priority_queue<Node, vector<Node>, greater<Node>> heap;  // 定义优先队列,小根堆
vector<Node> h[N];  // 定义邻接表,存储图的边信息
int n, m;  // 定义节点数和边数
int dist[N];  // 定义数组,存储源点到每个节点的距离
bool st[N];  // 定义数组,标记每个节点的距离是否已确定

// Dijkstra最短路径算法实现,s为源点
void dijkstra(int s) {
    dist[s] = 0;  // 源点到自身的距离为0
    heap.push({0, s});  // 将源点加入优先队列
    while (!heap.empty()) {
        Node nd = heap.top();  // 取出优先队列中距离最小的节点
        heap.pop();
        int veid = nd.second, distance = nd.first;  // 获取节点编号和距离
        if (st[veid] == true) continue;  // 如果该节点的距离已确定,跳过
        st[veid] = true;  // 标记该节点的距离已确定
        for (int i = 0; i < h[veid].size(); i++) {  // 遍历该节点的所有邻边
            Node ndi = h[veid][i];
            int j = ndi.second, dis = ndi.first;  // 获取邻边的终点和权重
            if (dist[j] > dist[veid] + dis) {  // 如果通过当前节点到达邻边终点的距离更短
                dist[j] = dist[veid] + dis;  // 更新邻边终点的距离
                heap.push({dist[j], j});  // 将邻边终点加入优先队列
            }
        }
    }
    return;
}

int main() {
    cin >> n >> m;  // 读取节点数和边数
    memset(dist, 0x3f, sizeof(dist));  // 初始化所有节点的距离为无穷大
    while (m--) {
        int a, b, c;
        cin >> a >> b >> c;  // 读取每条边的起点、终点和权重
        h[a].push_back({c, b});  // 将边信息插入到邻接表中
    }
    dijkstra(1);  // 调用Dijkstra算法,从源点1开始计算最短路径
    if (dist[n] >= INF) dist[n] = -1;  // 如果目标节点n不可达,距离设为-1
    cout << dist[n] << endl;  // 输出从源点1到目标节点n的最短路径长度
    return 0;
}

#include <bits/stdc++.h>
using namespace std;

// 定义常量
const int N = 150010, M=N, INF=1e9; // N为节点数上限,M为边数上限,INF为无穷大

// 链式前向星的数据结构
int h[N], e[M], w[M], ne[M], idx; // h数组记录每个节点的第一条边,e数组记录边的终点,w数组记录边的权重,ne数组记录下一条边,idx为边的计数器
int dist[N]; // dist数组记录从起点到每个节点的最短距离
bool st[N]; // st数组记录节点是否已确定最短路径

int n, m; // n为节点数,m为边数
typedef pair<int, int> Edge; // 定义边的类型为pair,first为距离,second为节点编号
priority_queue<Edge, vector<Edge>, greater<Edge> > heap; // 优先队列,用于Dijkstra算法的堆优化

// 添加边的函数
void add(int a, int b, int c) {
    e[idx]=b, w[idx]=c, ne[idx]=h[a], h[a]=idx++; // 将边(a, b)添加到链式前向星中
}

// Dijkstra算法实现
int dijkstra(int s) {
    dist[s] = 0; // 起点到起点的距离为0
    heap.push({0,s}); // 将起点加入优先队列
    while (!heap.empty()) {
        Edge nd=heap.top(); heap.pop(); // 取出距离最小的节点
        int vid=nd.second, dis=nd.first;
        if (st[vid]) continue; // 如果该节点已确定最短路径,跳过
        st[vid] = true; // 标记该节点已确定最短路径
        for (int i=h[vid]; i!=-1; i=ne[i]) { // 遍历该节点的所有邻接边
            int j=e[i];
            if (!st[j] && dist[j]>dist[vid]+w[i]) { // 如果邻接节点未确定最短路径且通过当前节点到达该节点的距离更短
                dist[j] = dist[vid] + w[i]; // 更新最短距离
                heap.push({dist[j], j}); // 将邻接节点加入优先队列
            }
        }
    }
    if (dist[n]==INF) dist[n]=-1; // 如果终点不可达,距离设为-1
    return dist[n]; // 返回从起点到终点的最短距离
}

int main() {
    fill(dist, dist+N, INF); // 初始化dist数组为无穷大
    memset(h, -1, sizeof(h)); // 初始化h数组为-1,表示没有边
    cin >> n >> m; // 输入节点数和边数
    while (m--) {
        int a, b, c;
        cin >> a >> b >> c; // 输入边的起点、终点和权重
        add(a, b, c); // 添加边到链式前向星中
    }
    cout << dijkstra(1) << endl; // 从节点1开始计算最短路径,并输出到节点n的最短距离
    return 0;
}

【运行结果】

3 3
1 2 2
2 3 1
1 3 4
3
posted @ 2026-06-27 14:20  团爸讲算法  阅读(14)  评论(0)    收藏  举报