Dijkstra 算法:求解非负权图的最短路径
Dijkstra 算法是一种用于查找图中两个顶点之间最短路径的算法。与 Bellman-Ford 算法相比,Dijkstra 算法的主要优势在于其效率,特别是在处理具有非负权重的边时。优化后的 Dijkstra 算法(通常使用优先队列)的时间复杂度通常优于 Bellman-Ford 算法(O(mn)),可以达到 O(m log n)。
Dijkstra 算法的核心思想是:每次从一个尚未确定最短路径的顶点集合中,选择一个距离起点最近的顶点。然后,基于这个选定的顶点,更新其所有相邻顶点的最短路径估计值。这个过程会一直持续,直到所有顶点都被访问过,此时起点到图中任意顶点的最短路径就已经确定。
为了高效地选择距离起点最近的未访问顶点,通常会使用一个最小堆(优先队列)来实现。堆中存储的是顶点及其到起点的当前最短距离估计值。
示例 1:Dijkstra 算法模板题
给定一个包含 n 个顶点和 m 条边的有向简单图,边的权重均为非负整数。需要处理 k 组查询,每组查询包含两个顶点 x 和 y,要求计算从 x 到 y 的最短路径长度。如果不存在路径,则输出 -1。
输入格式:
第一行包含三个整数 n, m, k,分别表示顶点数、边数和查询次数。
接下来 m 行,每行包含三个整数 x, y, z,表示从顶点 x 到顶点 y 存在一条权重为 z 的有向边。
接下来 k 行,每行包含两个整数 x, y,表示一组查询。
输出格式:
输出共 k 行,每行一个整数,表示对应查询的最短路径长度。
样例输入:
3 3 2 1 2 3 2 3 2 3 2 1 1 3 3 1
样例输出:
5 -1
数据规模:
2 ≤ n ≤ 100000, 0 ≤ m ≤ 200000, 1 ≤ k ≤ 5, 1 ≤ x, y ≤ n, 1 ≤ z ≤ 10000
代码实现:
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
typedef pair<int, int> EdgeInfo; // {neighbor_node, weight}
const int MAXN = 100005;
const int INF = 0x3f3f3f3f;
vector<EdgeInfo> adj[MAXN]; // Adjacency list: adj[u] stores pairs {v, w} for edges u -> v with weight w
int shortestDist[MAXN]; // shortestDist[i] stores the shortest distance from source to node i
int n_nodes, m_edges, k_queries;
// Function to compute shortest paths from a given source node
void computeShortestPaths(int start_node) {
// Initialize distances to infinity
memset(shortestDist, 0x3f, sizeof(shortestDist));
shortestDist[start_node] = 0;
// Min-priority queue storing pairs {distance, node}
priority_queue<EdgeInfo, vector<EdgeInfo>, greater<EdgeInfo>> pq;
pq.push({0, start_node});
while (!pq.empty()) {
int current_dist = pq.top().first;
int u = pq.top().second;
pq.pop();
// If we found a shorter path already, skip
if (current_dist > shortestDist[u]) {
continue;
}
// Relax edges outgoing from u
for (const auto& edge : adj[u]) {
int v = edge.first;
int weight = edge.second;
if (shortestDist[v] > shortestDist[u] + weight) {
shortestDist[v] = shortestDist[u] + weight;
pq.push({shortestDist[v], v});
}
}
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> n_nodes >> m_edges >> k_queries;
// Build the graph
for (int i = 0; i < m_edges; ++i) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
}
// Process queries
while (k_queries--) {
int query_start, query_end;
cin >> query_start >> query_end;
computeShortestPaths(query_start);
int result = shortestDist[query_end];
if (result == INF) {
cout << "-1" << endl;
} else {
cout << result << endl;
}
}
return 0;
}
示例 2:寻找最佳居住点
在一个有 n 个顶点和 m 条边的无向图中,每条边的权重都为非负整数。需要找到一个顶点作为居住点,使得该点到指定的三个地点(商场、工作地点、医院)的距离之和最小。这三个地点由三个顶点 a, b, c 表示。
输入格式:
第一行包含两个整数 n, m,表示图的顶点数和边数。
接下来 m 行,每行包含三个整数 x, y, z,表示顶点 x 和 y 之间存在一条权重为 z 的边。
下一行包含三个整数 a, b, c,分别表示商场、工作地点和医院的顶点编号。
输出格式:
输出一个整数,表示到商场、工作地点和医院的最小距离之和。
样例输入:
4 3 1 2 1 2 3 1 3 4 1 1 2 4
样例输出:
3
数据规模:
3 ≤ n ≤ 5000, 0 ≤ m ≤ 10000, 1 ≤ x, y, a, b, c ≤ n, 1 ≤ z ≤ 10000。图连通,且 a, b, c 互不相同。
解题思路:
为了解决这个问题,我们可以分别以 a, b, c 作为起点,运行三次 Dijkstra 算法。这会生成三个距离数组:`dist_a` (从 a 到各点的最短距离),`dist_b` (从 b 到各点的最短距离),和 `dist_c` (从 c 到各点的最短距离)。然后,遍历图中的每一个顶点 i,计算 `dist_a[i] + dist_b[i] + dist_c[i]`,并找出这个和的最小值。
代码实现:
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int, int> EdgeInfo; // {neighbor_node, weight}
const int MAXN = 5005;
const int MAXM_NODES = 10005; // Adjusted for max nodes, not edges
const int INF = 0x3f3f3f3f;
vector<EdgeInfo> adj[MAXN]; // Adjacency list
int dist_from_a[MAXN];
int dist_from_b[MAXN];
int dist_from_c[MAXN];
int current_dist[MAXN]; // Temporary storage for Dijkstra results
int mall_node, work_node, hospital_node;
// Dijkstra function, stores results in current_dist
void run_dijkstra(int start_node) {
memset(current_dist, 0x3f, sizeof(current_dist));
current_dist[start_node] = 0;
priority_queue<EdgeInfo, vector<EdgeInfo>, greater<EdgeInfo>> pq;
pq.push({0, start_node});
while (!pq.empty()) {
int d = pq.top().first;
int u = pq.top().second;
pq.pop();
if (d > current_dist[u]) {
continue;
}
for (const auto& edge : adj[u]) {
int v = edge.first;
int weight = edge.second;
if (current_dist[v] > current_dist[u] + weight) {
current_dist[v] = current_dist[u] + weight;
pq.push({current_dist[v], v});
}
}
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n_nodes, m_edges;
cin >> n_nodes >> m_edges;
// Build the undirected graph
for (int i = 0; i < m_edges; ++i) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w}); // Add edge in both directions for undirected graph
}
cin >> mall_node >> work_node >> hospital_node;
// Run Dijkstra from mall
run_dijkstra(mall_node);
memcpy(dist_from_a, current_dist, sizeof(dist_from_a));
// Run Dijkstra from work
run_dijkstra(work_node);
memcpy(dist_from_b, current_dist, sizeof(dist_from_b));
// Run Dijkstra from hospital
run_dijkstra(hospital_node);
memcpy(dist_from_c, current_dist, sizeof(dist_from_c));
int min_total_dist = INF;
// Find the node that minimizes the sum of distances
for (int i = 1; i <= n_nodes; ++i) {
if (dist_from_a[i] != INF && dist_from_b[i] != INF && dist_from_c[i] != INF) {
min_total_dist = min(min_total_dist, dist_from_a[i] + dist_from_b[i] + dist_from_c[i]);
}
}
cout << min_total_dist << endl;
return 0;
}
示例 3:有向图中的双向最短路径
在一个有 n 个顶点和 m 条边的有向图中,每条边的权重都为非负整数。需要找到一个顶点 k,使得从 k 出发,访问所有其他顶点并返回 k 的总时间(即从 k 到任意顶点 i 的最长路径,加上从任意顶点 i 返回 k 的最长路径)最小。
解题思路:
这个问题可以分解为两个子问题:
- 计算从源顶点 k 到图中任意顶点 i 的最短路径。
- 计算从任意顶点 i 到源顶点 k 的最短路径。
为了计算从任意顶点 i 到 k 的最短路径,我们可以反转所有边的方向,然后以 k 为起点运行 Dijkstra 算法。这样,在反向图上从 k 到 i 的最短路径,就等同于在原图上从 i 到 k 的最短路径。
设 `dist_to_k[i]` 表示从 k 到 i 的最短距离,`dist_from_k[i]` 表示从 i 到 k 的最短距离。
1. 运行 Dijkstra 算法,以 k 为源点,在原图上计算 `dist_to_k`。
2. 构建一个反向图,其中所有边的方向都被颠倒。然后,以 k 为源点,在反向图上运行 Dijkstra 算法,计算 `dist_from_k`。
最终,遍历图中的每一个顶点 i,计算 `dist_to_k[i] + dist_from_k[i]`。我们需要找到这个和的最大值。
代码实现:
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
typedef pair<int, int> EdgeInfo; // {neighbor_node, weight}
const int MAXN = 100005;
const long long INF = 0x3f3f3f3f3f3f3f3fLL; // Use long long for distances
vector<EdgeInfo> forward_adj[MAXN]; // Adjacency list for original graph
vector<EdgeInfo> backward_adj[MAXN]; // Adjacency list for reversed graph
long long dist_to_k[MAXN]; // Stores shortest distance FROM k TO i
long long dist_from_k[MAXN]; // Stores shortest distance FROM i TO k
int n_nodes, m_edges, k_source;
// Dijkstra function for forward graph
void compute_forward_shortest_paths(int start_node) {
memset(dist_to_k, 0x3f, sizeof(dist_to_k));
dist_to_k[start_node] = 0;
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
pq.push({0, start_node});
while (!pq.empty()) {
long long d = pq.top().first;
int u = pq.top().second;
pq.pop();
if (d > dist_to_k[u]) {
continue;
}
for (const auto& edge : forward_adj[u]) {
int v = edge.first;
int weight = edge.second;
if (dist_to_k[v] > dist_to_k[u] + weight) {
dist_to_k[v] = dist_to_k[u] + weight;
pq.push({dist_to_k[v], v});
}
}
}
}
// Dijkstra function for backward graph
void compute_backward_shortest_paths(int start_node) {
memset(dist_from_k, 0x3f, sizeof(dist_from_k));
dist_from_k[start_node] = 0;
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
pq.push({0, start_node});
while (!pq.empty()) {
long long d = pq.top().first;
int u = pq.top().second;
pq.pop();
if (d > dist_from_k[u]) {
continue;
}
for (const auto& edge : backward_adj[u]) {
int v = edge.first;
int weight = edge.second;
if (dist_from_k[v] > dist_from_k[u] + weight) {
dist_from_k[v] = dist_from_k[u] + weight;
pq.push({dist_from_k[v], v});
}
}
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> n_nodes >> m_edges >> k_source;
// Build both forward and backward graphs
for (int i = 0; i < m_edges; ++i) {
int u, v, w;
cin >> u >> v >> w;
forward_adj[u].push_back({v, w});
backward_adj[v].push_back({u, w}); // Reverse edge for backward graph
}
// Compute shortest paths from k
compute_forward_shortest_paths(k_source);
// Compute shortest paths to k (by running Dijkstra on reversed graph from k)
compute_backward_shortest_paths(k_source);
long long max_total_time = 0;
// Find the maximum sum of distances
for (int i = 1; i <= n_nodes; ++i) {
// Check if both paths exist
if (dist_to_k[i] != INF && dist_from_k[i] != INF) {
max_total_time = max(max_total_time, dist_to_k[i] + dist_from_k[i]);
}
}
cout << max_total_time << endl;
return 0;
}