用C语言手搓迪杰斯特拉算法:从邻接矩阵到最短路径的完整实现
很多C语言学习者在接触图论算法时,都会遇到一个共同的困境:理论课上听得明明白白,一到自己动手实现就无从下手。迪杰斯特拉算法作为最短路径问题的经典解法,其核心思想并不复杂,但要将这个思想转化为可运行的C语言代码,却需要跨越数据结构设计、内存管理、算法逻辑实现等多重障碍。
这篇文章就是为你准备的实战指南。我不会重复那些教科书上的理论推导,而是带你从零开始,一步步构建一个完整的迪杰斯特拉算法实现。我们会从最基础的邻接矩阵设计讲起,涵盖动态内存分配、算法核心循环、路径回溯等关键环节,每个步骤都配有可直接运行的代码片段和详细解释。无论你是正在学习数据结构的学生,还是想巩固算法基础的开发者,这篇文章都能让你真正掌握“手搓”迪杰斯特拉算法的能力。
1. 理解问题:从现实场景到图论抽象
在开始写代码之前,我们先要搞清楚迪杰斯特拉算法到底解决什么问题。想象一下城市交通导航:你从A点出发,要找到到达B、C、D等各个地点的最短行车路线。城市道路构成了一张“图”,路口是“顶点”,道路是“边”,行车时间就是边的“权重”。
迪杰斯特拉算法的核心任务就是:给定一个起点,计算出它到图中所有其他顶点的最短路径。这里有几个关键限制需要注意:
- 单源最短路径:算法一次只能计算从一个起点出发的最短路径
- 非负权值:所有边的权重必须大于等于0,否则算法可能失效
- 有向/无向图:算法对两种图都适用,实现时稍有区别
注意:如果你需要计算图中任意两点间的最短路径,需要运行n次迪杰斯特拉算法(n为顶点数),或者使用弗洛伊德算法。本文聚焦于单源最短路径的实现。
让我们用一个简单的例子来直观感受算法的执行过程。假设有5个城市(顶点0-4),道路连接和行驶时间如下:
| 起点 | 终点 | 时间 |
|---|---|---|
| 0 | 1 | 10 |
| 0 | 3 | 30 |
| 0 | 4 | 100 |
| 1 | 2 | 50 |
| 2 | 4 | 10 |
| 3 | 2 | 20 |
| 3 | 4 | 60 |
从城市0出发,迪杰斯特拉算法会逐步确定到每个城市的最短时间。这个过程就像“波浪扩散”:先确定最近的城市(城市1,时间10),然后基于这个城市更新其他城市的可达时间,如此反复直到所有城市都被“征服”。
2. 数据结构设计:邻接矩阵的C语言实现
在C语言中实现图算法,首先要解决的是图的存储问题。邻接矩阵是最直观的存储方式,特别适合稠密图(边数接近顶点数平方)。它的核心思想是用一个二维数组表示顶点间的连接关系。
2.1 图结构体定义
我们先定义一个完整的图结构体,包含所有必要的信息:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define MAX_VERTICES 100 // 最大顶点数限制
#define INF INT_MAX // 用最大整数表示无穷大
typedef struct {
int num_vertices; // 顶点数量
int num_edges; // 边数量
int **adj_matrix; // 邻接矩阵(二维数组)
char *vertex_names; // 顶点名称(可选)
} Graph;
这里有几个设计考虑:
- 使用
int **adj_matrix而不是固定大小的二维数组,为了支持动态图大小 INF用INT_MAX表示,避免魔法数字- 顶点名称字段是可选的,方便调试时识别顶点
2.2 动态内存分配:二维数组的正确姿势
很多初学者在动态分配二维数组时会犯错,下面是最安全、最清晰的做法:
// 创建邻接矩阵
int** create_adj_matrix(int n) {
int **matrix = (int**)malloc(n * sizeof(int*));
if (!matrix) {
fprintf(stderr, "内存分配失败\n");
exit(EXIT_FAILURE);
}
for (int i = 0; i < n; i++) {
matrix[i] = (int*)malloc(n * sizeof(int));
if (!matrix[i]) {
// 释放已分配的内存
for (int j = 0; j < i; j++) {
free(matrix[j]);
}
free(matrix);
fprintf(stderr, "内存分配失败\n");
exit(EXIT_FAILURE);
}
// 初始化:对角线为0,其他为INF
for (int j = 0; j < n; j++) {
matrix[i][j] = (i == j) ? 0 : INF;
}
}
return matrix;
}
这段代码有几个关键点:
- 先分配指针数组,再为每一行分配空间
- 每次分配后都检查是否成功
- 分配失败时正确释放已分配的内存(避免内存泄漏)
- 初始化时将对角线设为0(顶点到自身的距离)
2.3 完整的图创建函数
现在我们可以创建一个完整的图创建函数:
Graph* create_graph(int num_vertices) {
Graph *graph = (Graph*)malloc(sizeof(Graph));
if (!graph) {
fprintf(stderr, "图结构内存分配失败\n");
exit(EXIT_FAILURE);
}
graph->num_vertices = num_vertices;
graph->num_edges = 0;
graph->adj_matrix = create_adj_matrix(num_vertices);
// 可选:为顶点名称分配空间
graph->vertex_names = (char*)malloc(num_vertices * sizeof(char));
if (graph->vertex_names) {
for (int i = 0; i < num_vertices; i++) {
graph->vertex_names[i] = 'A' + i; // 默认名称A, B, C...
}
}
return graph;
}
添加边的函数也很重要:
void add_edge(Graph *graph, int src, int dest, int weight, int directed) {
if (src < 0 || src >= graph->num_vertices ||
dest < 0 || dest >= graph->num_vertices) {
fprintf(stderr, "顶点索引越界\n");
return;
}
graph->adj_matrix[src][dest] = weight;
graph->num_edges++;
// 如果是无向图,添加反向边
if (!directed && src != dest) {
graph->adj_matrix[dest][src] = weight;
// 注意:无向图的一条边在邻接矩阵中占两个位置,
// 但我们在num_edges中只计数一次,这是常见的做法
}
}
3. 迪杰斯特拉算法的核心实现
有了图的数据结构,我们现在可以实现迪杰斯特拉算法了。算法的核心思想可以用下面的伪代码概括:
1. 初始化距离数组dist[],起点为0,其他为无穷大
2. 初始化已访问数组visited[],全部为false
3. 初始化前驱数组prev[],记录最短路径
4. 重复以下步骤n次(n为顶点数):
a. 找到未访问顶点中距离最小的顶点u
b. 标记u为已访问
c. 更新u的所有邻居的距离:
如果 dist[u] + weight(u, v) < dist[v],则更新
3.1 基础版本实现
我们先看一个最直接的实现,虽然效率不是最优,但最容易理解:
void dijkstra_basic(Graph *graph, int start, int *dist, int *prev) {
int n = graph->num_vertices;
int *visited = (int*)calloc(n, sizeof(int));
if (!visited) {
fprintf(stderr, "内存分配失败\n");
return;
}
// 初始化
for (int i = 0; i < n; i++) {
dist[i] = graph->adj_matrix[start][i];
prev[i] = (dist[i] != INF && i != start) ? start : -1;
visited[i] = 0;
}
dist[start] = 0;
visited[start] = 1;
// 主循环:每次找到一个最短路径顶点
for (int i = 1; i < n; i++) {
// 步骤1:找到未访问顶点中距离最小的
int min_dist = INF;
int u = -1;
for (int v = 0; v < n; v++) {
if (!visited[v] && dist[v] < min_dist) {
min_dist = dist[v];
u = v;
}
}
// 如果没有可达的未访问顶点,结束
if (u == -1) break;
// 步骤2:标记为已访问
visited[u] = 1;
// 步骤3:更新邻居的距离
for (int v = 0; v < n; v++) {
if (!visited[v] && graph->adj_matrix[u][v] != INF) {
int new_dist = dist[u] + graph->adj_matrix[u][v];
if (new_dist < dist[v]) {
dist[v] = new_dist;
prev[v] = u;
}
}
}
}
free(visited);
}
这个实现的时间复杂度是O(V²),其中V是顶点数。对于稠密图(边数接近V²),这已经是最优的了。但对于稀疏图,我们可以优化。
3.2 使用最小堆优化查找最小距离顶点
当图比较稀疏时,大部分时间花在查找最小距离顶点上。使用最小堆(优先队列)可以将这个操作从O(V)降到O(logV),整体复杂度降到O((V+E)logV)。
下面是使用最小堆的优化版本:
// 最小堆节点结构
typedef struct {
int vertex;
int distance;
} HeapNode;
// 最小堆结构
typedef struct {
HeapNode *nodes;
int size;
int capacity;
int *positions; // 记录每个顶点在堆中的位置
} MinHeap;
// 创建最小堆
MinHeap* create_min_heap(int capacity) {
MinHeap *heap = (MinHeap*)malloc(sizeof(MinHeap));
heap->nodes = (HeapNode*)malloc(capacity * sizeof(HeapNode));
heap->positions = (int*)malloc(capacity * sizeof(int));
heap->size = 0;
heap->capacity = capacity;
for (int i = 0; i < capacity; i++) {
heap->positions[i] = -1;
}
return heap;
}
// 堆的辅助函数:交换、上浮、下沉等
void swap_nodes(HeapNode *a, HeapNode *b) {
HeapNode temp = *a;
*a = *b;
*b = temp;
}
void min_heapify(MinHeap *heap, int idx) {
int smallest = idx;
int left = 2 * idx + 1;
int right = 2 * idx + 2;
if (left < heap->size && heap->nodes[left].distance < heap->nodes[smallest].distance)
smallest = left;
if (right < heap->size && heap->nodes[right].distance < heap->nodes[smallest].distance)
smallest = right;
if (smallest != idx) {
// 更新位置信息
heap->positions[heap->nodes[smallest].vertex] = idx;
heap->positions[heap->nodes[idx].vertex] = smallest;
// 交换节点
swap_nodes(&heap->nodes[smallest], &heap->nodes[idx]);
// 递归调整
min_heapify(heap, smallest);
}
}
// 使用最小堆的迪杰斯特拉算法
void dijkstra_with_heap(Graph *graph, int start, int *dist, int *prev) {
int n = graph->num_vertices;
MinHeap *heap = create_min_heap(n);
// 初始化
for (int v = 0; v < n; v++) {
dist[v] = INF;
prev[v] = -1;
heap->nodes[v].vertex = v;
heap->nodes[v].distance = INF;
heap->positions[v] = v;
}
// 设置起点
dist[start] = 0;
heap->nodes[start].distance = 0;
heap->size = n;
// 构建初始堆
for (int i = n / 2 - 1; i >= 0; i--) {
min_heapify(heap, i);
}
// 主循环
while (heap->size > 0) {
// 提取最小距离顶点
HeapNode min_node = heap->nodes[0];
int u = min_node.vertex;
// 将最小节点移到末尾并减小堆大小
heap->nodes[0] = heap->nodes[heap->size - 1];
heap->positions[heap->nodes[0].vertex] = 0;
heap->positions[u] = -1; // u已不在堆中
heap->size--;
min_heapify(heap, 0);
// 如果u的距离是INF,说明剩下的顶点都不可达
if (dist[u] == INF) break;
// 更新u的所有邻居
for (int v = 0; v < n; v++) {
if (graph->adj_matrix[u][v] != INF) {
int new_dist = dist[u] + graph->adj_matrix[u][v];
if (new_dist < dist[v]) {
dist[v] = new_dist;
prev[v] = u;
// 更新堆中的距离
int pos = heap->positions[v];
if (pos != -1) {
heap->nodes[pos].distance = new_dist;
// 上浮调整堆
while (pos > 0 && heap->nodes[pos].distance <
heap->nodes[(pos - 1) / 2].distance) {
int parent = (pos - 1) / 2;
// 更新位置信息
heap->positions[heap->nodes[pos].vertex] = parent;
heap->positions[heap->nodes[parent].vertex] = pos;
// 交换节点
swap_nodes(&heap->nodes[pos], &heap->nodes[parent]);
pos = parent;
}
}
}
}
}
}
free(heap->nodes);
free(heap->positions);
free(heap);
}
这个版本虽然代码更长,但对于大规模稀疏图,性能提升非常明显。在实际项目中,你需要根据图的特性选择适合的实现。
4. 路径回溯与结果输出
计算完最短距离后,我们通常还需要知道具体的路径。前驱数组prev[]记录了每个顶点的前一个顶点,我们可以通过回溯来重建完整路径。
4.1 路径回溯函数
void print_path(int *prev, int start, int end) {
if (end == start) {
printf("%d", start);
return;
}
if (prev[end] == -1) {
printf("没有从%d到%d的路径", start, end);
return;
}
// 递归打印路径
print_path(prev, start, prev[end]);
printf(" -> %d", end);
}
// 非递归版本
void print_path_iterative(int *prev, int start, int end) {
int path[MAX_VERTICES];
int path_len = 0;
int current = end;
// 从终点回溯到起点
while (current != -1) {
path[path_len++] = current;
current = prev[current];
if (current == start) {
path[path_len++] = current;
break;
}
}
// 检查是否有有效路径
if (path[path_len - 1] != start) {
printf("没有从%d到%d的路径\n", start, end);
return;
}
// 反向打印路径
printf("路径: ");
for (int i = path_len - 1; i > 0; i--) {
printf("%d -> ", path[i]);
}
printf("%d\n", path[0]);
}
4.2 完整的测试示例
让我们用一个完整的例子来测试我们的实现:
void test_dijkstra() {
// 创建图(使用前面的例子)
Graph *graph = create_graph(5);
// 添加边
add_edge(graph, 0, 1, 10, 1); // 有向边
add_edge(graph, 0, 3, 30, 1);
add_edge(graph, 0, 4, 100, 1);
add_edge(graph, 1, 2, 50, 1);
add_edge(graph, 2, 4, 10, 1);
add_edge(graph, 3, 2, 20, 1);
add_edge(graph, 3, 4, 60, 1);
// 分配结果数组
int *dist = (int*)malloc(graph->num_vertices * sizeof(int));
int *prev = (int*)malloc(graph->num_vertices * sizeof(int));
if (!dist || !prev) {
fprintf(stderr, "内存分配失败\n");
return;
}
// 运行迪杰斯特拉算法
int start_vertex = 0;
dijkstra_basic(graph, start_vertex, dist, prev);
// 打印结果
printf("从顶点%d出发的最短路径:\n", start_vertex);
printf("顶点\t距离\t路径\n");
printf("----\t----\t----\n");
for (int i = 0; i < graph->num_vertices; i++) {
if (i == start_vertex) continue;
printf("%d\t", i);
if (dist[i] == INF) {
printf("INF\t不可达\n");
} else {
printf("%d\t", dist[i]);
print_path_iterative(prev, start_vertex, i);
}
}
// 验证结果
printf("\n验证关键路径:\n");
printf("0->4的最短距离应该是60,实际是%d\n", dist[4]);
printf("路径应该是0->3->2->4,实际是: ");
print_path_iterative(prev, 0, 4);
// 清理
free(dist);
free(prev);
// 释放图内存
for (int i = 0; i < graph->num_vertices; i++) {
free(graph->adj_matrix[i]);
}
free(graph->adj_matrix);
free(graph->vertex_names);
free(graph);
}
运行这个测试,你会看到类似下面的输出:
从顶点0出发的最短路径:
顶点 距离 路径
---- ---- ----
1 10 路径: 0 -> 1
2 50 路径: 0 -> 3 -> 2
3 30 路径: 0 -> 3
4 60 路径: 0 -> 3 -> 2 -> 4
验证关键路径:
0->4的最短距离应该是60,实际是60
路径应该是0->3->2->4,实际是: 路径: 0 -> 3 -> 2 -> 4
5. 高级话题与性能优化
5.1 处理大规模图的策略
当顶点数非常多时(比如超过10000),即使是O(V²)的算法也会变得很慢。这时候可以考虑以下优化策略:
- 邻接表代替邻接矩阵:对于稀疏图,邻接表可以大幅减少内存使用和遍历时间
- 双向迪杰斯特拉算法:从起点和终点同时搜索,在中间相遇
- A*算法:如果有启发式函数(如地理距离),可以优先搜索有希望的方向
5.2 邻接表实现示例
虽然本文重点在邻接矩阵,但了解邻接表的实现也很有价值:
// 邻接表节点
typedef struct AdjListNode {
int dest;
int weight;
struct AdjListNode* next;
} AdjListNode;
// 邻接表
typedef struct {
AdjListNode** lists; // 数组,每个元素是一个链表头
int num_vertices;
} AdjListGraph;
// 添加边到邻接表
void adj_list_add_edge(AdjListGraph* graph, int src, int dest, int weight, int directed) {
AdjListNode* new_node = (AdjListNode*)malloc(sizeof(AdjListNode));
new_node->dest = dest;
new_node->weight = weight;
new_node->next = graph->lists[src];
graph->lists[src] = new_node;
if (!directed) {
AdjListNode* reverse_node = (AdjListNode*)malloc(sizeof(AdjListNode));
reverse_node->dest = src;
reverse_node->weight = weight;
reverse_node->next = graph->lists[dest];
graph->lists[dest] = reverse_node;
}
}
使用邻接表的迪杰斯特拉算法在更新邻居时,只需要遍历每个顶点的邻接链表,而不是整个顶点数组。
5.3 内存管理与错误处理
在实际项目中,健壮的内存管理和错误处理至关重要:
// 安全的图创建函数
Graph* create_graph_safe(int num_vertices) {
if (num_vertices <= 0 || num_vertices > MAX_VERTICES) {
fprintf(stderr, "无效的顶点数: %d\n", num_vertices);
return NULL;
}
Graph* graph = (Graph*)malloc(sizeof(Graph));
if (!graph) {
perror("分配图结构失败");
return NULL;
}
graph->num_vertices = num_vertices;
graph->num_edges = 0;
// 分配邻接矩阵
graph->adj_matrix = (int**)malloc(num_vertices * sizeof(int*));
if (!graph->adj_matrix) {
perror("分配邻接矩阵行指针失败");
free(graph);
return NULL;
}
// 分配每一行
for (int i = 0; i < num_vertices; i++) {
graph->adj_matrix[i] = (int*)calloc(num_vertices, sizeof(int));
if (!graph->adj_matrix[i]) {
perror("分配邻接矩阵行失败");
// 清理已分配的行
for (int j = 0; j < i; j++) {
free(graph->adj_matrix[j]);
}
free(graph->adj_matrix);
free(graph);
return NULL;
}
// 初始化
for (int j = 0; j < num_vertices; j++) {
graph->adj_matrix[i][j] = (i == j) ? 0 : INF;
}
}
return graph;
}
5.4 性能对比与选择指南
不同的实现在不同场景下性能差异很大,下面是一个简单的对比表格:
| 实现方式 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 基础邻接矩阵 | O(V²) | O(V²) | 稠密图,V较小 |
| 邻接矩阵+最小堆 | O((V+E)logV) | O(V²) | 稀疏图,V中等 |
| 邻接表+最小堆 | O((V+E)logV) | O(V+E) | 稀疏图,V较大 |
| 邻接表+斐波那契堆 | O(E + VlogV) | O(V+E) | 极大稀疏图 |
选择建议:
- 如果顶点数少于1000,且图比较稠密,用基础邻接矩阵最简单
- 如果顶点数在1000-10000之间,用邻接矩阵+最小堆
- 如果顶点数超过10000,或者图非常稀疏,用邻接表+最小堆
6. 实际应用与扩展
迪杰斯特拉算法不仅仅是学术练习,它在实际中有广泛的应用:
6.1 网络路由
在网络路由协议(如OSPF)中,迪杰斯特拉算法用于计算最短路径树,确定数据包的最佳转发路径。每个路由器维护一个链路状态数据库,运行迪杰斯特拉算法找到到所有其他路由器的最短路径。
6.2 交通导航
地图应用如Google Maps、百度地图的核心算法之一就是迪杰斯特拉算法的变种。虽然实际系统会考虑实时交通、单行道、转弯限制等复杂因素,但基础仍然是最短路径算法。
6.3 社交网络分析
在社交网络中,迪杰斯特拉算法可以用于计算两个人之间的“最短连接路径”(六度分隔理论)。每个用户是顶点,关注/好友关系是边(有时带权重,如亲密度)。
6.4 扩展:带约束的最短路径
有时我们需要在满足某些约束条件下找最短路径,比如:
- 最多经过k条边
- 避开某些区域
- 必须经过某些点
这些可以通过修改迪杰斯特拉算法来实现。例如,对于“最多经过k条边”的约束,我们可以使用动态规划版本的迪杰斯特拉:
// 带跳数限制的迪杰斯特拉
void dijkstra_with_hops(Graph *graph, int start, int max_hops, int *dist) {
int n = graph->num_vertices;
// dist[h][v]:经过最多h跳到达v的最短距离
int **dp_dist = (int**)malloc((max_hops + 1) * sizeof(int*));
for (int h = 0; h <= max_hops; h++) {
dp_dist[h] = (int*)malloc(n * sizeof(int));
for (int v = 0; v < n; v++) {
dp_dist[h][v] = INF;
}
}
// 初始化:0跳时只能到达起点
for (int h = 0; h <= max_hops; h++) {
dp_dist[h][start] = 0;
}
// 动态规划
for (int h = 1; h <= max_hops; h++) {
for (int u = 0; u < n; u++) {
if (dp_dist[h-1][u] != INF) {
for (int v = 0; v < n; v++) {
if (graph->adj_matrix[u][v] != INF) {
int new_dist = dp_dist[h-1][u] + graph->adj_matrix[u][v];
if (new_dist < dp_dist[h][v]) {
dp_dist[h][v] = new_dist;
}
}
}
}
}
// 合并结果:h跳的最优也是h-1跳的最优
for (int v = 0; v < n; v++) {
if (dp_dist[h-1][v] < dp_dist[h][v]) {
dp_dist[h][v] = dp_dist[h-1][v];
}
}
}
// 复制最终结果
for (int v = 0; v < n; v++) {
dist[v] = dp_dist[max_hops][v];
}
// 清理
for (int h = 0; h <= max_hops; h++) {
free(dp_dist[h]);
}
free(dp_dist);
}
这个扩展展示了迪杰斯特拉算法的灵活性。通过结合动态规划,我们可以处理各种约束条件。
7. 调试技巧与常见问题
在实现迪杰斯特拉算法时,有几个常见的陷阱需要注意:
7.1 整数溢出问题
当边的权重很大或者路径很长时,距离相加可能导致整数溢出。解决方法:
// 安全的距离加法
int safe_add(int a, int b) {
if (a == INF || b == INF) return INF;
if (a > INT_MAX - b) return INF; // 避免溢出
return a + b;
}
// 在算法中使用
int new_dist = safe_add(dist[u], graph->adj_matrix[u][v]);
if (new_dist < dist[v]) {
dist[v] = new_dist;
prev[v] = u;
}
7.2 负权边检测
迪杰斯特拉算法不能处理负权边。如果图中可能有负权边,应该先检查:
int has_negative_weights(Graph *graph) {
for (int i = 0; i < graph->num_vertices; i++) {
for (int j = 0; j < graph->num_vertices; j++) {
if (graph->adj_matrix[i][j] < 0) {
return 1;
}
}
}
return 0;
}
// 在使用迪杰斯特拉算法前检查
if (has_negative_weights(graph)) {
fprintf(stderr, "错误:图中包含负权边,迪杰斯特拉算法不适用\n");
fprintf(stderr, "请使用Bellman-Ford算法\n");
return;
}
7.3 性能分析与优化
对于大型图,性能分析很重要。可以添加计时和统计代码:
#include <time.h>
void dijkstra_with_stats(Graph *graph, int start, int *dist, int *prev) {
clock_t start_time = clock();
int operations = 0;
// ... 算法实现 ...
// 在关键位置增加计数
operations++; // 例如在每次比较时
clock_t end_time = clock();
double elapsed = (double)(end_time - start_time) / CLOCKS_PER_SEC;
printf("算法统计:\n");
printf(" 运行时间: %.6f秒\n", elapsed);
printf(" 操作次数: %d\n", operations);
printf(" 平均每秒操作: %.0f\n", operations / elapsed);
}
7.4 可视化调试
对于复杂的图,可视化可以帮助调试。可以生成Graphviz格式的输出:
void export_to_dot(Graph *graph, int *dist, int *prev, const char* filename) {
FILE *dot_file = fopen(filename, "w");
if (!dot_file) {
perror("无法创建dot文件");
return;
}
fprintf(dot_file, "digraph G {\n");
fprintf(dot_file, " rankdir=LR;\n");
fprintf(dot_file, " node [shape=circle];\n\n");
// 添加节点和距离标签
for (int i = 0; i < graph->num_vertices; i++) {
fprintf(dot_file, " %d [label=\"%d\\ndist=%d\"];\n",
i, i, dist[i] == INF ? -1 : dist[i]);
}
fprintf(dot_file, "\n");
// 添加最短路径边(加粗显示)
for (int i = 0; i < graph->num_vertices; i++) {
if (prev[i] != -1) {
fprintf(dot_file, " %d -> %d [penwidth=2, color=red];\n", prev[i], i);
}
}
fprintf(dot_file, "\n");
// 添加所有其他边
for (int i = 0; i < graph->num_vertices; i++) {
for (int j = 0; j < graph->num_vertices; j++) {
if (graph->adj_matrix[i][j] != INF && i != j && prev[j] != i) {
fprintf(dot_file, " %d -> %d [label=\"%d\", color=gray];\n",
i, j, graph->adj_matrix[i][j]);
}
}
}
fprintf(dot_file, "}\n");
fclose(dot_file);
printf("图已导出到 %s\n", filename);
printf("使用命令查看: dot -Tpng %s -o graph.png && open graph.png\n", filename);
}
这个函数会生成一个DOT文件,可以用Graphviz工具转换为图像,直观显示最短路径。
实现迪杰斯特拉算法的过程中,最让我有成就感的是看到算法正确找到最短路径的那一刻。但真正让代码健壮起来,是在处理了各种边界情况之后:图不连通时的处理、内存分配失败的恢复、大规模数据时的性能优化。这些实战经验比单纯理解算法原理更有价值,也是区分初学者和有经验开发者的关键。如果你在实现过程中遇到问题,不妨从最简单的5个顶点的小图开始,一步步调试,观察每个数组的变化,这样对算法的理解会更加深刻。
9739



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



