好多东西忘记了。。
需要提前了解一下知识:
1.邻接表(稀疏与稠密)
稠密图的核心定义是边数 M 接近或达到顶点数平方级O(N^2),
稀疏图的核心定义是是M<<(远小于)O(N^2)
2.头插法连接链表
将新节点 / 新边插入到链表的头部,而非尾部
若节点
a原本的邻接边链表是:边2 → 边1 → 边0(h[a]=2),添加新边(索引 3)后,链表变为:边3 → 边2 → 边1 → 边0(h[a]=3),新边始终在表头位置。
一.朴素版Dijkstra--O(N2)
适用于稠密图:边数接近 (N^2)
1.建表
2.遍历所有节点,每次找出未确定的距离最小的点,更新最短距离
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510;
const int INF = 0x3f3f3f3f;
int n, m;
int g[N][N]; // 邻接矩阵存储无向图(g[a][b] = g[b][a])
int dist[N];//从起点到节点 i 的当前最短路径长度
bool st[N];
int dijkstra() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;//设置节点1为起点
for (int i = 0; i < n; i++) {
// 找到未确定最短路径的距离最小节点
int u = -1;
for (int j = 1; j <= n; j++) {
if (!st[j] && (u == -1 || dist[j] < dist[u])) {
u = j;
}
}
st[u] = true;
// 找出最小路径
for (int v = 1; v <= n; v++) {
if (dist[v] > dist[u] + g[u][v]) {
dist[v] = dist[u] + g[u][v];
}
}
}
return dist[n] == INF ? -1 : dist[n];//返回1-》n的最短路径
}
int main() {
memset(g, 0x3f, sizeof g);
cin >> n >> m;
while (m--) {
int a, b, c;
cin >> a >> b >> c;
// 无向图:双向赋值,且保留最小边权(处理重边)
g[a][b] = min(g[a][b], c);
g[b][a] = min(g[b][a], c);
}
cout << dijkstra() << endl;
return 0;
}
二.堆优化版Dijkstra
适用于稀疏图
若图是稠密图(M约等于N^2),朴素 Dijkstra 算法(每次遍历所有节点找最小值,时间复杂度 O(N^2)反而更优:
- 稠密图(M约等于N^2),堆优化的 O(M log N) 会退化为 O(N^2 log N),比朴素版慢;
- 此时可直接用数组遍历找最小值,无需堆结构。
#include<bits/stdc++.h>
#define PII pair<int,int> // 定义PII类型,存储(距离, 节点)对
using namespace std;
const int N=2000, M=2*N; // N为最大节点数,M为最大边数(无向图边数翻倍)
int n,m; // n为节点数,m为边数
// 邻接表存储:e[]存边的终点,ne[]存下一条边的索引
//h[]存每个节点的邻接表头,w[]存边权,idx 边的唯一标识
int e[M], ne[M], h[N], idx, w[M];
// 添加边的函数:a为起点,b为终点,c为边权
void add(int a, int b, int c)
{
e[idx] = b; // 当前边的终点
w[idx] = c; // 当前边的权重
ne[idx] = h[a];// 下一条边的索引指向原a的邻接表头
h[a] = idx++; // 更新a的邻接表头为当前边的索引
}
int dist[N]; // dist[i]表示起点到节点i的最短距离
bool st[N]; // st[i]标记节点i是否已确定最短路径
// Dijkstra算法(堆优化版)
int dijkstra()
{
// 优先队列(小顶堆):存储(距离, 节点),按距离升序排列
priority_queue<PII, vector<PII>, greater<PII>> q;
memset(dist, 0x3f, sizeof(dist));
dist[1] = 0; // 起点(节点1)到自身的距离为0
q.push({0, 1}); // 起点入队
while(q.size()) // 队列非空时循环
{
auto t = q.top(); q.pop(); // 取出距离最小的节点
int ver = t.second; // 当前节点编号
if(st[ver]) continue; // 若已确定最短路径,跳过
st[ver] = true; // 标记为已确定
// 遍历当前节点的所有邻接边
for(int i = h[ver]; ~i; i = ne[i])
{
int j = e[i]; // 邻接边的终点
// 松弛操作:若通过当前节点到j的距离更短,则更新
if(dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
q.push({dist[j], j}); // 新的距离和节点入队
}
}
}
// 若终点n不可达,返回-1;否则返回最短距离
if(dist[n] != 0x3f3f3f3f)
return dist[n];
else
return -1;
}
int main()
{
cin >> n >> m; // 输入节点数和边数
memset(h, -1, sizeof(h)); // 初始化邻接表头为-1(表示无邻接边)
while(m--)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c); // 添加正向边
add(b, a, c); // 添加反向边(无向图)
}
cout << dijkstra(); // 执行算法并输出结果
return 0;
}
三.js版本堆优化
// 定义最大节点数和边数(根据原题设定)
const N = 2000;
const M = 2 * N;
let e = new Array(M).fill(0);
let w = new Array(M).fill(0);
let ne = new Array(M).fill(0);
let h = new Array(N).fill(-1);
let idx = 0;
function add(a, b, c) {
e[idx] = b; // 当前边的终点
w[idx] = c; // 当前边的权重
ne[idx] = h[a];// 下一条边的索引指向当前a的邻接表头
h[a] = idx++; // 更新a的邻接表头为当前边的索引
}
// 距离数组:dist[i]表示从起点到i的最短距离
let dist = new Array(N).fill(Infinity);
// 标记数组:st[i]表示i是否已确定最短距离
let st = new Array(N).fill(false);
function dijkstra(n) {
// 优先队列(小顶堆):存储[距离, 节点],按距离升序排列
let q = [];
dist[1] = 0; // 起点(节点1)到自身的距离为0
q.push([0, 1]);
while (q.length > 0) {
// 取出队列中距离最小的节点(模拟优先队列)
q.sort((a, b) => a[0] - b[0]); // 按距离升序排序
let [distance, ver] = q.shift(); // 取出队首元素
// 如果该节点已确定最短距离,跳过
if (st[ver]) continue;
st[ver] = true; // 标记为已确定
// 遍历当前节点的所有邻接边
for (let i = h[ver]; i !== -1; i = ne[i]) {
let j = e[i]; // 邻接边的终点
// 如果通过当前节点到达j的距离更短,更新距离
if (dist[j] > dist[ver] + w[i]) {
dist[j] = dist[ver] + w[i];
q.push([dist[j], j]);
}
}
}
// 如果终点n不可达,返回-1,否则返回最短距离
return dist[n] === Infinity ? -1 : dist[n];
}
// 主函数:通过readline实现用户输入
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let lines = []; // 存储用户输入的所有行
let ptr = 0; // 输入指针,用于逐行读取
rl.on('line', (line) => {
// 处理每行输入,去除首尾空格并过滤空行
const trimmedLine = line.trim();
if (trimmedLine) {
lines.push(trimmedLine);
}
});
rl.on('close', () => {
// 读取第一行:n和m(节点数和边数)
const [n, m] = lines[ptr++].split(' ').map(Number);
// 初始化邻接表
h.fill(-1);
idx = 0;
// 读取m条边
for (let i = 0; i < m; i++) {
const [a, b, c] = lines[ptr++].split(' ').map(Number);
add(a, b, c); // 添加正向边
add(b, a, c); // 添加反向边(无向图)
}
// 执行Dijkstra算法并输出结果
console.log(dijkstra(n));
});
js版本--并不是真正的堆优化
四.js版本的问题
1. 优先队列的实现问题
在 C++ 版本中,使用的是二叉堆实现的优先队列(priority_queue),其push和pop操作的时间复杂度为 O(log n),是高效的优化方式。而 JavaScript 版本中,通过数组排序模拟优先队列(每次shift()前调用sort()),存在两个问题:
sort()的时间复杂度为 O(k log k)(k 为队列当前长度),远高于二叉堆的 O(log k);shift()操作会移动数组所有元素,时间复杂度为(O(k),进一步降低效率。
因此,JavaScript 版本的优先队列实现并未真正优化,属于 “伪优化”,仅能处理小规模数据(如题目中的 N=2000,大规模数据下会超时。
2. 真正的优化方向
若要实现和 C++ 版本一致的高效优化,JavaScript 中需手动实现二叉堆(小顶堆),替代数组排序的方式
3329

被折叠的 条评论
为什么被折叠?



