Dijkstra模板

Dijkstra 朴素版 O(n^2)

解决什么样的问题

Dijkstra 算法用于求解单源最短路径问题,适用于边权非负的有向图或无向图。算法通过贪心策略逐步确定从起点到各顶点的最短路径。

以 849Dijkstra 来描述朴素版的Dijkstra 算法 O(n^2)复杂度

https://www.acwing.com/problem/content/851/

算法思路

  1. 初始化:记录起点到各节点的距离,起点距离为 0,其余初始化为无穷大。
  2. 贪心选择:每次从未处理的节点中选出距离起点最近的节点。
  3. 松弛操作:用该节点更新其邻接节点的距离(若更短)。
  4. 标记完成:已处理的节点不再参与后续计算。
  5. 重复:直到所有节点均被处理。

算法流程

  1. 输入处理与初始化
    • 使用邻接矩阵g[][]存储边权,初始化为 INF(表示无穷大)。
    • 处理重边时,保留最短边:g[x][y] = min(g[x][y], z)
    • 初始化距离数组 dist[]dist[1] = 0,其余为 INF
    • 标记数组 st[] 记录节点是否已处理。
  2. 主循环(进行 n 次)
    • 选择当前最短节点:遍历所有节点,找到未处理且距离最小的节点 t
    • 标记已处理st[t] = true
    • 松弛邻接节点:遍历所有节点 j,尝试通过 t 缩短路径:
      dist[j] = min(dist[j], dist[t] + g[t][j])
  3. 输出结果
    • 若终点 dist[n] 仍为 INF,说明不可达,输出 -1
    • 否则输出 dist[n]

关键细节

  • 邻接矩阵的初始化
    所有边初始化为 INF,读入时处理重边,确保保留最短边。
  • 贪心选择的实现
    每次遍历所有节点,选择未处理的最小距离节点。时间复杂度为 O(n²),适合节点数较少的情况(如本题 n ≤ 500)。
  • 松弛操作的逻辑
    即使 g[t][j]INF(无边),dist[j] 不会被错误更新,因为 dist[t] + INF 仍为极大值,不影响 min 操作。
  • 无穷大的设置
    使用 0x3f3f3f3f(约 1e9)表示 INF,其两倍不会溢出,且便于判断未达节点。
  • 处理自环与重边
    自环不影响结果(正权边无法缩短路径),重边通过取最小值处理。

常见问题

  • 为什么不能处理负权边?
    负权可能导致已处理的节点通过更短路径重新被访问,破坏贪心策略的正确性。
  • 如何处理不可达的情况?
    若终点距离仍为初始化的 INF,则不可达。
  • 为何选择邻接矩阵?
    节点数较少(n ≤ 500),邻接矩阵实现简单且效率可接受。边数较多时,需改用堆优化(O(m log n))。

code

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 5e2 + 10; // 最大节点数+10的缓冲区
int n, m;               // n: 节点数, m: 边数
int g[N][N];            // 邻接矩阵存储图结构(处理稠密图)
int dist[N];            // 存储起点到各点的最短距离
bool st[N];             // 标记节点是否已确定最短路径

int Dijkstra()
{
    memset(dist, 0x3f, sizeof dist); // 初始化所有距离为无穷大
    dist[1] = 0;                     // 起点到自身的距离为0

    // 进行n次迭代,每次确定一个节点的最短路径
    for (int i = 1; i <= n; i++)
    {
        int t = -1;
        // 找出当前未确定最短路径的节点中距离最小的
        for (int j = 1; j <= n; j++)
        {
            if (!st[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        }

        st[t] = true; // 标记该节点已确定最短路径

        // 通过当前节点t更新其他节点的距离
        for (int j = 1; j <= n; j++)
        {
            // 松弛操作:尝试用 dist[t] + g[t][j] 更新 dist[j]
            dist[j] = min(dist[j], dist[t] + g[t][j]);
        }
    }

    // 判断终点是否可达
    if (dist[n] == 0x3f3f3f3f)
        return -1;
    return dist[n];
}

int main()
{
    cin >> n >> m;
    memset(g, 0x3f, sizeof g); // 初始化邻接矩阵为无穷大

    // 处理输入边,注意处理重边保留最小值
    for (int i = 1; i <= m; i++)
    {
        int x, y, z;
        cin >> x >> y >> z;
        g[x][y] = min(g[x][y], z); // 对于重边,只保留最短的边
    }

    int ans = Dijkstra();
    cout << ans << endl;

    return 0;
}

Dijkstra 堆优化版 O(nlogn)

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;

const int N = 150005; // 最大节点数+10的缓冲区
int n, m;             // n: 节点数, m: 边数
int dist[N];          // 存储起点到各点的最短距离
bool st[N];           // 标记节点是否已确定最短路径

typedef pair<int, int> PII;

int ne[N], h[N], e[N], w[N], idx;

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int Dijkstra()
{
    memset(dist, 0x3f, sizeof dist); // 初始化所有距离为无穷大
    dist[1] = 0;                     // 起点到自身的距离为0
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});
    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();
        int distance = t.first, ver = t.second;
        if (st[ver])
            continue;
        st[ver] = true;
        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > distance + w[i])
            {
                dist[j] = distance + w[i];
                heap.push({dist[j], j});
            }
        }
    }
    // 判断终点是否可达
    if (dist[n] == 0x3f3f3f3f)
        return -1;
    return dist[n];
}

int main()
{
    memset(h, -1, sizeof h);
    cin >> n >> m;
    for (int i = 1; i <= m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    int ans = Dijkstra();
    cout << ans << endl;
    return 0;
}
posted @ 2025-02-05 13:10  hongjianMa  阅读(214)  评论(0)    收藏  举报