641 字
2 分钟
Dijkstra
2026-03-18
无标签
  • Dijkstra
  • 本算法用于求解图中两点的最短距离,要求无负权边 本质是贪心思想,将图中的点分成两类,一类为未确定最短距离的点, 另一类为已经确定的点,为了方便我将其称之为A点和B点。
  1. 首先将所有点初始化为B点,边长度为无穷大
  2. 读入边,起点和终点
  3. 将起点加入A点
  4. 在所有B点中,找到一个距起点距离最短的点,以这个点为中介来松弛其他边,接着把他加入A点
  5. 循环以上操作,直到无法进行下去

以下给出点权和优先的迪杰斯特拉算法代码模板,记录路径条数

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int INF = 0x3f3f3f3f;
int n, m, st, ed;
int g[510][510]; // 邻接矩阵存储边权
int weight[510]; // 每个点的点权(如救援队数量)
int dist[510]; // 从起点到 i 的最短距离
int num[510]; // 从起点到 i 的最短路径条数
int w[510]; // 从起点到 i 的最大点权和
bool vis[510]; // 标记是否已确定最短路
void dijkstra(int s) {
// 1. 初始化
fill(dist, dist + 510, INF);
dist[s] = 0;
num[s] = 1;
w[s] = weight[s];
for (int i = 0; i < n; i++) {
// 2. 找到当前未访问过的最近的点
int u = -1, min_d = INF;
for (int j = 0; j < n; j++) {
if (!vis[j] && dist[j] < min_d) {
u = j;
min_d = dist[j];
}
}
if (u == -1) return; // 剩余点不可达,直接退出
vis[u] = true;
// 3. 松弛操作 (Relaxation)
for (int v = 0; v < n; v++) {
if (!vis[v] && g[u][v] != INF) {
if (dist[u] + g[u][v] < dist[v]) {
// 发现更短的路
dist[v] = dist[u] + g[u][v];
num[v] = num[u]; // 路径数继承
w[v] = w[u] + weight[v]; // 点权和继承
} else if (dist[u] + g[u][v] == dist[v]) {
// 发现等长的路
num[v] += num[u]; // 路径数累加
if (w[u] + weight[v] > w[v]) {
w[v] = w[u] + weight[v]; // 更新最大点权
}
}
}
}
}
}
int main() {
cin >> n >> m >> st >> ed;
for (int i = 0; i < n; i++) cin >> weight[i];
// 矩阵初始化为 INF
fill(g[0], g[0] + 510 * 510, INF);
while (m--) {
int u, v, l;
cin >> u >> v >> l;
g[u][v] = g[v][u] = l; // 无向图
}
dijkstra(st);
cout << num[ed] << " " << w[ed] << endl;
return 0;
}

注意:统计路径条数时num[i]+=num[j],也就是说必须继承自上一个点

实际操作的时候,我并未注意,导致犯错

  • 错误代码示例鉴赏
for (int v = 0; v < n; v++)
{
if (g[u][v] != INF && !vis[v])
{
if (dist[v] > dist[u] + g[u][v])
dist[v] = dist[u] + g[u][v], num[v] = num[u];
if (dist[v] == dist[u] + g[u][v])//这里会使得最后路径条数翻倍,想想为什么
num[v]+=num[u];
}
}
  • 以下是PTA-GPLT真题113 AC code
#include <bits/stdc++.h>
using namespace std;
int n, m, s, d;
int val[501], vmax[501], num[501];
int g[501][501];
int vis[501];
int dist[501];
int INF = 0x3f3f3f3f;
int pre[501];
int main()
{
cin >> n >> m >> s >> d;
for (int i = 0; i <= 500; i++)
for (int j = 0; j < 500; j++)
g[i][j] = INF;
for (int i = 0; i <= 500; i++)
dist[i] = INF;
for (int i = 0; i < n; i++)
cin >> val[i];
for (int i = 1; i <= m; i++)
{
int a, b, s;
cin >> a >> b >> s;
g[a][b] = g[b][a] = s;
}
int u = s;
vis[u] = 1, dist[u] = 0, num[u] = 1, vmax[u] = val[u];
for (int i = 1; i <= n; i++)
{
int id, len = INF, p = 0;
for (int v = 0; v < n; v++)
{
if (g[u][v] != INF && !vis[v])
{
if (dist[v] > dist[u] + g[u][v])
{
dist[v] = dist[u] + g[u][v], num[v] = num[u];
vmax[v] = vmax[u] + val[v];
pre[v] = u;
}
else if (dist[v] == dist[u] + g[u][v])
{
num[v] += num[u];
if (vmax[v] < vmax[u] + val[v])
pre[v] = u, vmax[v] = vmax[u] + val[v];
}
}
}
for (int v = 0; v < n; v++)
{
if (vis[v] == 0 && dist[v] < len)
len = dist[v], id = v, p = 1;
}
if (p)
u = id, vis[u] = 1;
else
break;
}
cout << num[d] << " " << vmax[d] << endl;
vector<int> ans;
for (int i = d;; i = pre[i])
{
ans.push_back(i);
if (i == s)
break;
}
reverse(ans.begin(), ans.end());
for (int i=0;i<ans.size();i++){
cout << ans[i];
if(i!=ans.size()-1)
cout << " ";
}
return 0;
}

tips: 可以用fill函数简化初始化过程

  • 思维流程:

先不考虑限制,需要g[n][n]存储边长度,vis[n]对点分类,dist[n]存储最短距离

接着考虑限制,比如加入num数组统计条数,加入vmax统计点权最大,加入pre数组来保存路径

💡 Dijkstra 避坑四行诗 初始化要齐全:dist 设无穷,num[s] 拨到 1,起点权值先加上。

选点标记最优先:先在大海(未访问点)里捞针(最小 dist),捞出来立刻打上 vis 补丁。

松弛逻辑分两边:

更短路: 喜新厌旧,dist、num、vmax、pre 全部推翻重来(继承 u)。

等长路: 兼容并蓄,num 累加,vmax 和 pre 择优录取。

回溯路径逆着牵:从 d 开始找 pre[i],最后 reverse 一下最稳妥。

🛠️ 针对你这次“小错误”的复盘 你刚才遇到的几个坑,其实是所有初学者必踩的“勋章”:

逻辑重叠 (if 变 else if):这是对程序执行流的理解,踩过一次你这辈子都不会忘。

路径计数 (+= 还是 =):这是对“状态转移”的理解,是动态规划的思想。

变量名覆盖 (s 既是起点又是边权):这是工程习惯的磨炼。

  • Gemini整理的代码模板:
#include <bits/stdc++.h>
using namespace std;// 常量定义:510 留出一点余裕,0x3f 是竞赛常用的无穷大
const int MAXN = 510;
const int INF = 0x3f3f3f3f;
int n, m, st, ed;
int g[MAXN][MAXN]; // 邻接矩阵
int val[MAXN]; // 每个点的点权
int dist[MAXN]; // 最短距离
int num[MAXN]; // 最短路径条数
int vmax[MAXN]; // 最大点权和
int pre[MAXN]; // 路径回溯:记录当前点的前驱
bool vis[MAXN]; // 标记是否已访问
void dijkstra(int start) {
// 1. 使用 fill 或 memset 快速初始化
fill(dist, dist + MAXN, INF);
fill(num, num + MAXN, 0);
fill(vmax, vmax + MAXN, 0);
fill(vis, vis + MAXN, false);
dist[start] = 0;
num[start] = 1;
vmax[start] = val[start];
for (int i = 0; i < n; i++) {
// 2. 找到未访问点中距离最近的 u
int u = -1, min_d = INF;
for (int j = 0; j < n; j++) {
if (!vis[j] && dist[j] < min_d) {
u = j;
min_d = dist[j];
}
}
if (u == -1) break; // 剩余点不可达
vis[u] = true; // 标记 u 已确定
// 3. 松弛操作:用 u 更新邻居 v
for (int v = 0; v < n; v++) {
if (!vis[v] && g[u][v] != INF) {
if (dist[u] + g[u][v] < dist[v]) {
// 情况 A:发现更短的路,全部“继承”并“覆盖”
dist[v] = dist[u] + g[u][v];
num[v] = num[u];
vmax[v] = vmax[u] + val[v];
pre[v] = u;
}
else if (dist[u] + g[u][v] == dist[v]) {
// 情况 B:发现等长的路,路径数“累加”
num[v] += num[u];
// 在等长前提下,择优录取(最大点权)
if (vmax[u] + val[v] > vmax[v]) {
vmax[v] = vmax[u] + val[v];
pre[v] = u;
}
}
}
}
}
}
int main() {
// 加速输入输出(可选)
ios::sync_with_stdio(false);
cin.tie(0);
if (!(cin >> n >> m >> st >> ed)) return 0;
// 初始化地图为 INF
fill(g[0], g[0] + MAXN * MAXN, INF);
for (int i = 0; i < n; i++) cin >> val[i];
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
g[u][v] = g[v][u] = w; // 无向图
}
dijkstra(st);
// 输出第一行:路径数 和 最大点权
cout << num[ed] << " " << vmax[ed] << endl;
// 输出第二行:回溯路径
vector<int> path;
for (int i = ed; ; i = pre[i]) {
path.push_back(i);
if (i == st) break;
}
// 优雅的末尾无空格输出
for (int i = path.size() - 1; i >= 0; i--) {
cout << path[i] << (i == 0 ? "" : " ");
}
return 0;
}
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Dijkstra
https://caoyue.xin/posts/test/2026-03-18-dijkstra/
作者
Colton/曹越
发布于
2026-03-18
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录