C++实现最短路径算法详解
本文系统介绍了最短路径问题的分类、算法实现及应用。主要内容包括:1)问题分类(单源、单目标等)和常见算法(Dijkstra、Bellman-Ford等);2)图的两种存储结构(邻接矩阵和邻接表)的C++实现;3)详细解析Dijkstra、Bellman-Ford、Floyd-Warshall和A*四种核心算法的原理及代码实现;4)算法性能比较和应用场景分析;5)优化技巧与扩展应用。文章通过代码示例
1. 最短路径问题概述
最短路径问题是图论中的经典问题,旨在寻找图中两个顶点之间路径权值之和最小的路径。这类问题在现实生活中有着广泛的应用,如GPS导航、网络路由、物流配送等。
1.1 问题分类
最短路径问题主要分为以下几类:
- 单源最短路径:从一个顶点出发到其他所有顶点的最短路径
- 单目标最短路径:从所有顶点到某一个顶点的最短路径
- 单对顶点最短路径:两个特定顶点之间的最短路径
- 所有顶点对最短路径:图中所有顶点之间的最短路径
1.2 常见算法
常用的最短路径算法包括:
- Dijkstra算法:适用于非负权图
- Bellman-Ford算法:可处理负权边
- Floyd-Warshall算法:计算所有顶点对最短路径
- A*算法:启发式搜索算法
2. 图的表示方法
在C++中实现最短路径算法前,我们需要先了解图的表示方法。
2.1 邻接矩阵
邻接矩阵是使用二维数组表示图的方法,适合稠密图。
#include <iostream>
#include <vector>
using namespace std;
class GraphMatrix {
private:
int V; // 顶点数
vector<vector<int>> matrix;
public:
GraphMatrix(int vertices) : V(vertices) {
matrix.resize(V, vector<int>(V, 0));
}
void addEdge(int u, int v, int weight) {
matrix[u][v] = weight;
matrix[v][u] = weight; // 无向图
}
void printGraph() {
for (int i = 0; i < V; ++i) {
for (int j = 0; j < V; ++j) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
}
};
int main() {
GraphMatrix g(5);
g.addEdge(0, 1, 2);
g.addEdge(0, 2, 4);
g.addEdge(1, 2, 1);
g.addEdge(1, 3, 7);
g.addEdge(2, 4, 3);
g.addEdge(3, 4, 1);
g.printGraph();
return 0;
}
邻接矩阵实现简单,但空间复杂度为O(V²),不适合稀疏图。
2.2 邻接表
邻接表使用链表或动态数组存储每个顶点的邻接顶点,适合稀疏图。
<iostream>
#include <vector>
#include <list>
#include <utility> // for pair
using namespace std;
class GraphList {
private:
int V;
vector<list<pair<int, int>>> adj; // 邻接表: pair<顶点, 权重>
public:
GraphList(int vertices) : V(vertices), adj(V) {}
void addEdge(int u, int v, int weight) {
adj[u].push_back(make_pair(v, weight));
adj[v].push_back(make_pair(u, weight)); // 无向图
}
void printGraph() {
for (int i = 0; i < V; ++i) {
cout << "顶点 " << i << " 的邻接顶点: ";
for (auto it = adj[i].begin(); it != adj[i].end(); ++it) {
cout << "(" << it->first << ", " << it->second << ") ";
}
cout << endl;
}
}
};
int main() {
GraphList g(5);
g.addEdge(0, 1, 2);
g.addEdge(0, 2, 4);
g.addEdge(1, 2, 1);
g.addEdge(1, 3, 7);
g.addEdge(2, 4, 3);
g.addEdge(3, 4, 1);
g.printGraph();
return 0;
}
邻接表空间复杂度为O(V+E),更适合稀疏图。
3. Dijkstra算法实现
Dijkstra算法是解决单源最短路径问题的经典算法,适用于非负权图。
3.1 算法原理
- 初始化:设置源点到自身的距离为0,其他顶点距离为无穷大
- 选择当前距离最小的未访问顶点
- 更新该顶点邻接顶点的距离
- 重复步骤2-3,直到所有顶点都被访问
3.2 C++实现
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
typedef pair<int, int> iPair; // <距离, 顶点>
void dijkstra(const vector<vector<iPair>>& graph, int src) {
int V = graph.size();
vector<int> dist(V, INT_MAX);
priority_queue<iPair, vector<iPair>, greater<iPair>> pq;
dist[src] = 0;
pq.push(make_pair(0, src));
while (!pq.empty()) {
int u = pq.top().second;
pq.pop();
for (auto& neighbor : graph[u]) {
int v = neighbor.first;
int weight = neighbor.second;
if (dist[v] > dist[u] + weight) {
dist[v] = dist[u] + weight;
pq.push(make_pair(dist[v], v));
}
}
}
cout << "顶点\t距离源点的最短距离" << endl;
for (int i = 0; i < V; ++i) {
cout << i << "\t" << dist[i] << endl;
}
}
int main() {
int V = 5;
vector<vector<iPair>> graph(V);
// 构建图
graph[0].push_back(make_pair(1, 2));
graph[0].push_back(make_pair(2, 4));
graph[1].push_back(make_pair(0, 2));
graph[1].push_back(make_pair(2, 1));
graph[1].push_back(make_pair(3, 7));
graph[2].push_back(make_pair(0, 4));
graph[2].push_back(make_pair(1, 1));
graph[2].push_back(make_pair(4, 3));
graph[3].push_back(make_pair(1, 7));
graph[3].push_back(make_pair(4, 1));
graph[4].push_back(make_pair(2, 3));
graph[4].push_back(make_pair(3, 1));
dijkstra(graph, 0);
return 0;
}
Dijkstra算法时间复杂度为O((V+E)logV),使用优先队列优化。
4. Bellman-Ford算法实现
Bellman-Ford算法可以处理负权边,并能检测负权环。
4.1 算法原理
- 初始化所有顶点距离
- 对每条边进行V-1次松弛操作
- 检查是否存在负权环
4.2 C++实现
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
struct Edge {
int src, dest, weight;
};
void bellmanFord(vector<Edge>& edges, int V, int E, int src) {
vector<int> dist(V, INT_MAX);
dist[src] = 0;
// V-1次松弛
for (int i = 1; i <= V - 1; ++i) {
for (int j = 0; j < E; ++j) {
int u = edges[j].src;
int v = edges[j].dest;
int weight = edges[j].weight;
if (dist[u] != INT_MAX && dist[v] > dist[u] + weight) {
dist[v] = dist[u] + weight;
}
}
}
// 检查负权环
for (int i = 0; i < E; ++i) {
int u = edges[i].src;
int v = edges[i].dest;
int weight = edges[i].weight;
if (dist[u] != INT_MAX && dist[v] > dist[u] + weight) {
cout << "图中存在负权环!" << endl;
return;
}
}
cout << "顶点\t距离源点的最短距离" << endl;
for (int i = 0; i < V; ++i) {
cout << i << "\t" << dist[i] << endl;
}
}
int main() {
int V = 5;
int E = 8;
vector<Edge> edges = {
{0, 1, 2}, {0, 2, 4}, {1, 2, 1},
{1, 3, 7}, {2, 4, 3}, {3, 4, 1},
{1, 0, 2}, {2, 0, 4}
};
bellmanFord(edges, V, E, 0);
return 0;
}
Bellman-Ford算法时间复杂度为O(VE),适合处理负权边。
5. Floyd-Warshall算法实现
Floyd-Warshall算法用于计算所有顶点对之间的最短路径。
5.1 算法原理
- 初始化距离矩阵
- 通过中间顶点k更新所有顶点对(i,j)的距离
- 重复步骤2直到所有顶点都作为中间顶点被考虑
5.2 C++实现
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
#define INF INT_MAX
void printSolution(const vector<vector<int>>& dist, int V) {
cout << "所有顶点对的最短距离矩阵:" << endl;
for (int i = 0; i < V; ++i) {
for (int j = 0; j < V; ++j) {
if (dist[i][j] == INF)
cout << "INF ";
else
cout << dist[i][j] << " ";
}
cout << endl;
}
}
void floydWarshall(vector<vector<int>>& graph, int V) {
vector<vector<int>> dist(V, vector<int>(V));
// 初始化距离矩阵
for (int i = 0; i < V; ++i)
for (int j = 0; j < V; ++j)
dist[i][j] = graph[i][j];
// 更新距离矩阵
for (int k = 0; k < V; ++k) {
for (int i = 0; i < V; ++i) {
for (int j = 0; j < V; ++j) {
if (dist[i][k] != INF && dist[k][j] != INF &&
dist[i][j] > dist[i][k] + dist[k][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
printSolution(dist, V);
}
int main() {
int V = 4;
vector<vector<int>> graph = {
{0, 5, INF, 10},
{INF, 0, 3, INF},
{INF, INF, 0, 1},
{INF, INF, INF, 0}
};
floydWarshall(graph, V);
return 0;
}
Floyd-Warshall算法时间复杂度为O(V³),空间复杂度为O(V²)。
6. A*算法实现
A*算法是一种启发式搜索算法,常用于路径规划。
6.1 算法原理
- 使用估价函数f(n) = g(n) + h(n)
- g(n): 从起点到当前节点的实际代价
- h(n): 从当前节点到终点的估计代价(启发式函数)
- 维护开放列表和关闭列表
- 每次选择f(n)最小的节点扩展
6.2 C++实现
<iostream>
#include <vector>
#include <queue>
#include <cmath>
#include <unordered_set>
using namespace std;
struct Node {
int x, y;
double g, h;
Node* parent;
Node(int x, int y) : x(x), y(y), g(0), h(0), parent(nullptr) {}
double f() const { return g + h; }
};
struct CompareNode {
bool operator()(const Node* a, const Node* b) {
return a->f() > b->f();
}
};
double heuristic(int x1, int y1, int x2, int y2) {
return sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));
}
vector<Node*> aStar(const vector<vector<int>>& grid, Node* start, Node* end) {
int rows = grid.size();
int cols = grid[0].size();
priority_queue<Node*, vector<Node*>, CompareNode> openList;
unordered_set<Node*> closedList;
start->h = heuristic(start->x, start->y, end->x, end->y);
openList.push(start);
while (!openList.empty()) {
Node* current = openList.top();
openList.pop();
if (current->x == end->x && current->y == end->y) {
// 找到路径
vector<Node*> path;
while (current != nullptr) {
path.push_back(current);
current = current->parent;
}
return path;
}
closedList.insert(current);
// 检查四个方向的邻居
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
for (int i = 0; i < 4; ++i) {
int nx = current->x + dx[i];
int ny = current->y + dy[i];
if (nx < 0 || nx >= rows || ny < 0 || ny >= cols || grid[nx][ny] == 1)
continue;
Node* neighbor = new Node(nx, ny);
neighbor->parent = current;
neighbor->g = current->g + 1;
neighbor->h = heuristic(nx, ny, end->x, end->y);
bool inClosed = false;
for (auto node : closedList) {
if (node->x == nx && node->y == ny) {
inClosed = true;
break;
}
}
if (inClosed) continue;
openList.push(neighbor);
}
}
return {}; // 未找到路径
}
int main() {
vector<vector<int>> grid = {
{0, 0, 0, 0, 0},
{0, 1, 1, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 1, 1, 0},
{0, 0, 0, 0, 0}
};
Node* start = new Node(0, 0);
Node* end = new Node(4, 4);
vector<Node*> path = aStar(grid, start, end);
if (!path.empty()) {
cout << "找到路径:" << endl;
for (int i = path.size() - 1; i >= 0; --i) {
cout << "(" << path[i]->x << ", " << path[i]->y << ")";
if (i != 0) cout << " -> ";
}
cout << endl;
} else {
cout << "未找到路径" << endl;
}
return 0;
}
A*算法性能取决于启发式函数的选择,好的启发式函数能显著提高搜索效率。
7. 性能比较与应用场景
7.1 算法比较
| 算法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| Dijkstra | O((V+E)logV) | O(V) | 非负权图,单源最短路径 |
| Bellman-Ford | O(VE) | O(V) | 含负权边,单源最短路径 |
| Floyd-Warshall | O(V³) | O(V²) | 所有顶点对最短路径 |
| A* | 取决于启发式函数 | O(V) | 路径规划,启发式搜索 |
7.2 应用场景
- Dijkstra算法:GPS导航、网络路由协议(OSPF)
- Bellman-Ford算法:距离向量路由协议(RIP)、金融套利检测
- Floyd-Warshall算法:交通规划、社交网络分析
- A*算法:游戏AI路径规划、机器人导航
8. 优化技巧与扩展
8.1 优化技巧
-
Dijkstra算法优化:
- 使用斐波那契堆可将时间复杂度降至O(E + VlogV)
- 双向搜索可减少搜索空间
-
A*算法优化:
- 使用更精确的启发式函数
- 实现跳点搜索(JPS)优化
8.2 扩展应用
- K最短路径:寻找前K条最短路径
- 动态最短路径:处理图中边权动态变化的情况
- 多目标最短路径:考虑多个优化目标的最短路径
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)