Dijkstra 算法#图论

Dijkstra 算法

  • 算法前提:在没有负边的情况下使用。
  • 算法思路:将结点分成已确定最短路长度的点集 S 和未确定最短路长度的点集 T,每次从 T 集合中选取最短路长度最小的结点移到 S 集合中,并对其出边执行更新操作
  1. 从T集合中,选取一个最短路长度最小的结点,移到S集合中。
  2. 对那些刚刚被加入S集合的结点的所有出边执行松弛操作。
cpp 复制代码
struct edge { 
    int v, w; // 边的终点和权值
}; 
struct node { 
    int dis, u; // 距离和顶点
    bool operator > (const node &a ) const {
        return dis>a.dis; // 优先队列比较函数,小根堆
    }
}; 
vector<edge> e[MAXN]; // 邻接表存储边
int dis[MAXN], vis[MAXN]; // dis存储最短距离,vis标记是否已确定最短距离
priority_queue<node, vector<node>, greater<node>> q; // 小根堆优先队列

void dijkstra(int n, int s) {
    memset(dis, 0x3f, (n + 1) * sizeof(int)); // 初始化最短距离为无穷大
    memset(vis, 0, (n + 1) * sizeof(int)); // 初始化标记数组为0
    dis[s] = 0; // 起点到自身的距离为0
    q.push({0, s}); // 起点入队

    while (!q.empty()) {
        int u = q.top().u; // 取出距离最小的顶点
        q.pop(); 
        if (vis[u]) continue; // 如果已确定最短距离,跳过
        vis[u] = 1; // 标记为已确定最短距离

        for (auto ed : e[u]) { // 遍历u的所有出边
            int v = ed.v, w = ed.w; // 边的终点和权值
            // 如果可以通过u得到更短的距离到v
            if (dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w; // 更新最短距离
                q.push({dis[v], v}); // 将v入队
            }
        }
    }
}

例题

cpp 复制代码
#i#include <bits/stdc++.h>
using namespace std;

const int MAXN = 2010;  // 最大节点数(人数),题目中 n≤2000,这里多开一点防止越界
double dis[MAXN];       // 存储从起点 A 到各节点能保留的金额比例,初始化为极小值
bool vis[MAXN];         // 标记节点是否已确定最长路径,避免重复处理
int n, m, A, B;         // n 是总人数,m 是转账对数,A 是转账起点,B 是转账终点

// 边的结构体,用于构建图的邻接表,to 表示转账目标节点,rate 表示转账后能保留的比例(1 - 手续费比例)
struct Edge {
    int to;
    double rate;
};

vector<Edge> graph[MAXN];  // 邻接表存储图结构,graph[u] 存所有从 u 出发能转账到的节点及对应保留比例

// 优先队列中的节点结构体,用于 Dijkstra 算法,node 表示节点编号,val 表示到达该节点时能保留的金额比例
struct Node {
    int node;
    double val;
    // 重载小于运算符,让优先队列按照 val 从大到小排序(大顶堆),这样每次取出当前能保留比例最大的路径
    bool operator<(const Node& other) const {
        return val < other.val;
    }
};

// Dijkstra 算法实现,求解从 A 出发到各节点能保留的最大金额比例
void dijkstra() {
    // 初始化 dis 数组,将起点 A 的保留比例设为 1(还没转账,金额完整保留),其他设为极小值
    fill(dis, dis + MAXN, 0);
    dis[A] = 1;
    // 优先队列,大顶堆,用于每次选当前能保留比例最大的路径拓展
    priority_queue<Node> pq;
    pq.push({A, 1});

    while (!pq.empty()) {
        Node cur = pq.top();
        pq.pop();
        int u = cur.node;
        double currentRate = cur.val;

        // 如果当前节点已经处理过(已确定最长路径),跳过
        if (vis[u]) continue;
        vis[u] = true;  // 标记为已处理

        // 遍历当前节点 u 的所有邻接边,尝试更新邻接节点的最大保留比例
        for (const Edge& e : graph[u]) {
            int v = e.to;
            double newRate = currentRate * e.rate;
            // 如果经过 u 转账到 v 能获得更大的保留比例,就更新,并将 v 加入队列等待处理
            if (newRate > dis[v]) {
                dis[v] = newRate;
                pq.push({v, newRate});
            }
        }
    }
}

int main() {
    // 输入总人数 n 和转账对数 m
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; ++i) {
        int x, y, z;
        // 输入转账的两人 x、y 以及手续费比例 z
        scanf("%d%d%d", &x, &y, &z);
        double rate = 1 - z / 100.0;  // 计算转账后能保留的比例(1 - 手续费比例)
        // 因为是互相转账,所以添加两条有向边(x 到 y 和 y 到 x)
        graph[x].push_back({y, rate});
        graph[y].push_back({x, rate});
    }
    // 输入转账起点 A 和终点 B
    scanf("%d%d", &A, &B);

    dijkstra();  // 执行 Dijkstra 算法,计算从 A 到各节点的最大保留比例

    // B 要收到 100 元,那么 A 需要的初始金额 = 100 / (A 到 B 能保留的比例)
    double result = 100 / dis[B];
    // 输出结果,精确到小数点后 8 位
    printf("%.8lf\n", result);

    return 0;
}
相关推荐
随缘而动,随遇而安42 分钟前
第八十八篇 大数据中的递归算法:从俄罗斯套娃到分布式计算的奇妙之旅
大数据·数据结构·算法
IT古董1 小时前
【第二章:机器学习与神经网络概述】03.类算法理论与实践-(3)决策树分类器
神经网络·算法·机器学习
水木兰亭4 小时前
数据结构之——树及树的存储
数据结构·c++·学习·算法
Jess075 小时前
插入排序的简单介绍
数据结构·算法·排序算法
老一岁5 小时前
选择排序算法详解
数据结构·算法·排序算法
xindafu5 小时前
代码随想录算法训练营第四十二天|动态规划part9
算法·动态规划
xindafu5 小时前
代码随想录算法训练营第四十五天|动态规划part12
算法·动态规划
freexyn6 小时前
Matlab自学笔记六十一:快速上手解方程
数据结构·笔记·matlab
一定要AK6 小时前
2025—暑期训练一
算法