dijkstra算法

Dijkstra 算法

  • dijkstra 算法可以用来解决单源最短路径问题
  • dijkstra算法不能求带负权边的最短路径
  • 有向图或无向图均可

原理

它的核心思想是贪心策略:每次从 “未确定最短路径” 的顶点中选择距离起点最近的顶点,确定其最短路径后,再以该顶点为中介,更新其他顶点的距离。

  1. 核心思想
    维护两个关键集合:

    • 已确定最短路径的顶点集(S):这些顶点到起点的最短距离已最终确定,不会再被修改。
    • 未确定最短路径的顶点集(U):这些顶点到起点的距离是当前已知的临时值,可能会被更新。

    算法通过以下步骤迭代:
    ① 从 U 中选择距离起点最近的顶点u,将其加入 S;
    ② 以u为中介点,更新 U 中所有与u相邻的顶点v的距离(若u→v的路径比当前v的距离更短,则更新);
    ③ 重复①②,直到所有顶点都加入 S。

  2. 为什么能处理非负权边?
    由于边权非负,当一个顶点u被加入 S 时,其到起点的距离已经是最短的 —— 因为任何后续路径(经过其他未访问顶点)的总权值只会更大(非负累加),不可能比当前距离更短。

通用模版(邻接矩阵)

Dijkstra() {
  初始化;
  for(循环n次) {
    u = 使dis[u]最小的还未被访问的顶点的编号;
    记u为确定值;
    for(从u出发能到达的所有顶点v){
      if(v未被访问 && 以u为中介点使s到顶点v的最短距离更优)
        优化dis[v];
    }
  }
}

实现步骤(详细流程)

假设图有n个顶点,起点为start,用dis[i]表示起点到顶点i的当前最短距离,visited[i]标记顶点i是否已加入集合 S。

步骤 1:初始化

  • 距离数组dis[i] = 无穷大(INF)(表示初始时无法到达),dis[start] = 0(起点到自身距离为 0)。
  • 访问标记visited[i] = false(所有顶点初始都在 U 中)。

步骤 2:迭代确定最短路径(共 n 次,每次确定一个顶点)

  1. 选择当前最短距离的顶点
    从 U(visited[i] = false)中找到dis[i]最小的顶点u,作为本次要加入 S 的顶点。
  2. 标记为已确定
    visited[u] = true(将u加入 S)。
  3. 松弛操作(更新相邻顶点距离)
    对所有与u相邻的顶点v(即存在边u→v),若v仍在 U 中(visited[v] = false),则检查:
    dis[v] > dis[u] + 边u→v的权重,则更新 dis[v] = dis[u] + 边u→v的权重(表示通过uv的路径更短)。

步骤 3:结束

当所有顶点都被加入 S(visited[i]全为true),dis数组中存储的就是起点到各顶点的最短距离。

代码实现(C++)

  1. 基础实现(邻接矩阵,时间复杂度 O (n²))

适用于顶点数较少(n≤1000)的稠密图。

  1. 优化实现(邻接表 + 优先队列,时间复杂度 O (m log n))

适用于顶点数多(n>1000)的稀疏图(边数少),用优先队列快速找到 “当前最短距离顶点”。

#include <iostream>
#include <vector>
#include <queue>  // 优先队列
#include <climits>
using namespace std;

const int INF = INT_MAX;

// 邻接表:每个顶点存储(相邻顶点, 边权重)
vector<int> dijkstra(int n, int start, const vector<vector<pair<int, int>>>& adj) {
    vector<int> dis(n, INF);  // 距离数组
    dis[start] = 0;

    // 优先队列(小根堆):存储(距离, 顶点),按距离升序排列
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
    pq.push({0, start});  // 起点入队

    while (!pq.empty()) {
        // 步骤1:取出当前距离最小的顶点u
        int u = pq.top().second;
        int current_dist = pq.top().first;
        pq.pop();

        // 若当前距离大于已知最短距离,说明u已被处理过,跳过
        if (current_dist > dis[u]) continue;

        // 步骤2:松弛u的所有相邻顶点
        for (const auto& edge : adj[u]) {
            int v = edge.first;    // 相邻顶点
            int weight = edge.second;  // 边权重

            // 若通过u到v的路径更短
            if (dis[v] > dis[u] + weight) {
                dis[v] = dis[u] + weight;
                pq.push({dis[v], v});  // 将新距离入队
            }
        }
    }

    return dis;
}

int main() {
    // 示例图(邻接表)
    int n = 4;
    vector<vector<pair<int, int>>> adj(n);
    // 无向图:添加双向边
    adj[0].push_back({1, 2});
    adj[1].push_back({0, 2});
    adj[0].push_back({2, 5});
    adj[2].push_back({0, 5});
    adj[1].push_back({2, 1});
    adj[2].push_back({1, 1});
    adj[1].push_back({3, 4});
    adj[3].push_back({1, 4});
    adj[2].push_back({3, 1});
    adj[3].push_back({2, 1});

    int start = 0;
    vector<int> shortest = dijkstra(n, start, adj);

    // 输出结果
    cout << "起点" << start << "到各顶点的最短距离:" << endl;
    for (int i = 0; i < n; i++) {
        if (shortest[i] == INF) {
            cout << "到" << i << ":不可达" << endl;
        } else {
            cout << "到" << i << ":" << shortest[i] << endl;
        }
    }

    return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值