用C语言手搓迪杰斯特拉算法:从邻接矩阵到最短路径的完整实现

校园导航系统(数据结构C语言邻接矩阵为存储结构和迪杰斯特拉算法求最短距离) 校园导航系统 开发工具:Dev-C++; (1)设计任务 掌握图的结构特点及应用,运用所学知识解决现实问题。给出校园各主要建筑的名称信息及有线路联通的建筑之间的距离,利用校园导航系统计算出输入的起点到终点之间的最近距离及线路。 (2)设计要求 ①输入各建筑信息及线路信息,构建图。本项目数据结构为有向网,采用邻接矩阵为存储结构。 ②计算给定起点到终点之间最近距离的进行线路。参照迪杰斯特拉算法计算给定... 阅读详情

用C语言手搓迪杰斯特拉算法:从邻接矩阵到最短路径的完整实现

很多C语言学习者在接触图论算法时,都会遇到一个共同的困境:理论课上听得明明白白,一到自己动手实现就无从下手。迪杰斯特拉算法作为最短路径问题的经典解法,其核心思想并不复杂,但要将这个思想转化为可运行的C语言代码,却需要跨越数据结构设计、内存管理、算法逻辑实现等多重障碍。

这篇文章就是为你准备的实战指南。我不会重复那些教科书上的理论推导,而是带你从零开始,一步步构建一个完整的迪杰斯特拉算法实现。我们会从最基础的邻接矩阵设计讲起,涵盖动态内存分配、算法核心循环、路径回溯等关键环节,每个步骤都配有可直接运行的代码片段和详细解释。无论你是正在学习数据结构的学生,还是想巩固算法基础的开发者,这篇文章都能让你真正掌握“手搓”迪杰斯特拉算法的能力。

1. 理解问题:从现实场景到图论抽象

在开始写代码之前,我们先要搞清楚迪杰斯特拉算法到底解决什么问题。想象一下城市交通导航:你从A点出发,要找到到达B、C、D等各个地点的最短行车路线。城市道路构成了一张“图”,路口是“顶点”,道路是“边”,行车时间就是边的“权重”。

迪杰斯特拉算法的核心任务就是:给定一个起点,计算出它到图中所有其他顶点的最短路径。这里有几个关键限制需要注意:

  • 单源最短路径:算法一次只能计算从一个起点出发的最短路径
  • 非负权值:所有边的权重必须大于等于0,否则算法可能失效
  • 有向/无向图:算法对两种图都适用,实现时稍有区别

注意:如果你需要计算图中任意两点间的最短路径,需要运行n次迪杰斯特拉算法(n为顶点数),或者使用弗洛伊德算法。本文聚焦于单源最短路径的实现。

让我们用一个简单的例子来直观感受算法的执行过程。假设有5个城市(顶点0-4),道路连接和行驶时间如下:

起点终点时间
0110
0330
04100
1250
2410
3220
3460

从城市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;
}

这段代码有几个关键点:

  1. 先分配指针数组,再为每一行分配空间
  2. 每次分配后都检查是否成功
  3. 分配失败时正确释放已分配的内存(避免内存泄漏)
  4. 初始化时将对角线设为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²)的算法也会变得很慢。这时候可以考虑以下优化策略:

  1. 邻接表代替邻接矩阵:对于稀疏图,邻接表可以大幅减少内存使用和遍历时间
  2. 双向迪杰斯特拉算法:从起点和终点同时搜索,在中间相遇
  3. 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个顶点的小图开始,一步步调试,观察每个数组的变化,这样对算法的理解会更加深刻。

c语言邻接矩阵存储有向网,使用迪杰斯特拉算法求最短路径并输出 #邻接矩阵存储有向网 在这里插入代码片 阅读详情

相关推荐

c语言实现求最短路径(迪杰斯特拉算法,《数据结构》算法7.15)

迪杰斯特拉算法从小到大的求出了从源点到其余各个点的最短路径,用到了邻接矩阵的储存结构。 代码如下:#include #define MAX_VERTEX_NUM 100 #define INFINITY 2000000000 typedef struct { int vexs[MAX_VERTEX_NUM]; int arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM];

LiQiyao的博客 9739

C语言-最短路径(Dijskra算法)

1. 顶点下标查找函数(LocateVex) 2. 创建有向网(CreateDN) 3. 打印图函数(print) 4. 展示最短路径函数(displayPath) 5. 查找当前最短路径函数(FindMinDist) 6. 迪杰斯特拉算法(ShortestPath_Dijkstra)

Attract's Blog 1万+

图解迪杰斯特拉(Dijkstra)算法(Java语言实现)

概述 感觉迪杰斯特拉好难,才学会的这个算法,所以写下博客加深理解。 Dijkstra算法是从一个顶点到其余各顶点的最短路径算法,解决的是带权图中最短路径问题。Dijkstra算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。 Dijkstra算法将图中的节点集分为: 1.最短路径计算完成的节点集S 2.最短路径未计算完成的节点集T 首先选择一个起点,然后每次将从T中挑选“起点->...

qq_40554133的博客 4931

C语言 最短路径 迪杰斯特拉(Dijkstra)算法

迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。

m0_73070900的博客 8144

c语言实现迪杰斯特拉算法(邻接表)

储存结构,结构体的定义:(权值w用于表示两点间路径的花费) typedef int Status; typedef struct ENode//图的邻接表定义 { int adjVex;//任意顶点u相邻接的顶点 int w;//边的权值 struct ENode* nextArc;//指向下一个边结点 }ENode; typedef struct LGraph { ...

李振da的碎碎念 2465

掌握C语言实现迪杰斯特拉算法的实用指南

迪杰斯特拉算法的主要步骤是:1. 选择源点,初始化源点到自身的最短距离为0,到所有其他点的距离为无穷大。2. 创建一个集合,用于存储已经找到最短路径的节点。3. 从源点开始,对图中所有未处理的节点进行循环,找到距离源点最近的节点。4. 更新当前节点的邻居节点的最短距离。5. 将当前节点添加到已处理节点集合中。6. 重复步骤3至5,直到所有节点的最短路径都被找到。

weixin_42573757的博客 1375

C语言:迪杰斯特拉(Dijkstra)算法

目录引言算法思想实现代码 引言 日常生活中,当我们出去旅游、办事等等,面对陌生的环境,我们常常需要规划自己的路线,来使移动的距离最短或是开销最低。那么,今天给大家简单介绍一种针对有向网图的最短路径算法——迪杰斯特拉算法。 最短路径:即起点和终点之间经过的边上权值(Weight)之和最小的路径 算法思想 迪杰斯拉算法类似贪心算法 实现 本篇图的存储采用邻接矩阵(Adjacency matrix) 代码 ...

NiGo的博客 7151

Arduino——GY39大气压、温湿度、光照模块

前几天买东西的时候买了一个GY39,这个模块集成了温湿度,大气压,海拔,光照一体,使用起来很方面。它有两种工作模式使用串口模式时需要搭配转接板,我选择了IIC模式,看一下通信协议吧,就是关于高八位和低八位的数据。看一下接线。

戚晨夕的博客 2605

图的最短路径--迪杰斯特拉算法 c语言

还是按照书上的例子: 完整代码如下: #include <stdio.h> #include <stdlib.h> #include <string.h> #define MaxInt 32767//无穷值设置 #define MVNum 100 //图的最大容量 ,也可以称为图的最大顶点数 void Interrupt(void)//创建一个中断函数 { while(1)//用于检测换行符,使函数脱离scanf的连续输出 if(getchar()=='

qq_43402544的博客 3926

Docker篇——Docker常用命令大全!史上最全面的Docker命令,涵盖常规命令、镜像基本命令、容器基本命令以及常用其他命令,一篇文章速通!

本篇文章介绍的关于DOcker的命令非常全面,Docker常见的命令介绍完毕,涉及到的命令比较繁杂,希望大家可以收藏并坚持学习。后续还会持续更新相关技术点,敬请期待

我热爱学习,也乐于分享。无论是科技前沿的洞见,还是生活中的小技巧,我都会在这里与你分享。我相信,知识就是力量,而分享则能让这份力量得到更好的传递。 5748

万象RK3506-EG1800网关使用说明

摘要:HD-RK3506-EG1800是一款高性能工业物联网边缘计算网关,支持Buildroot和Ubuntu双系统。文档详细介绍了网关的网络配置(包括ETH0/ETH1的静态IP设置、WiFi连接)、时钟同步、HSL软件安装配置(含开机自启动服务)、系统日志优化以及刷机操作指南(Loader/Maskrom模式)。特别提供了基于lighttpd的网页管理方案,通过HTML界面实现网络参数可视化配置,包含身份验证、实时状态显示和表单提交功能。网关默认ETH0固定IP为192.168.1.10,提供完整的CL

tian_jiangnan的博客 176

c语言迪杰斯特拉算法求最短路径,迪杰斯特拉 ( Dijkstra ) 最短路径算法

迪杰斯特拉算法介绍迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。基本思想通过Dijkstra计算图G中的最短路径时,需要指定起点s(即从顶点s开始计算)。此外,引进两个集合S和U。S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而U则是记录还未求出最短路径的...

weixin_42295892的博客 4104

防火墙断网后Adobe软件还弹窗“THIS APP HAS BEEN DISABLED”?原来是科学上网导致的问题!用这个简单方法一键解决!

很多小伙伴在使用上网工具的时候时,突然遇到Adobe弹出“THIS APP HAS BEEN DISABLED”的烦人提示,这个非正版弹窗真的让人头疼!即使你之前尝试过,有时也不奏效,究其原因,原来是因为你使用了代理网络,这时Adobe的流量并不是走本地,而是走代理,导致防火墙的拦截无效。不用慌!今天我来教大家如何通过上网工具,轻松禁用Adobe验证的联网流量,彻底解决这个问题!

qq_34093397的博客 3万+

hnust 1809: 算法7-15:迪杰斯特拉最短路径算法

hnust 1809: 算法7-15:迪杰斯特拉最短路径算法

weixin_50950742的博客 1053

PVE虚拟机运行不流畅,出现卡顿。

🏆本文收录于《优快云问答解惑-专业版》专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

**My Coding Family** 3065

顶刊论文引言用AI怎么写?借助Gemini 3 Pro结合四段式写出逻辑清晰的Introduction

写研究目标时,先明确研究问题,直接回应前文提到的研究缺口,如 “This study aims to investigate whether X modulates Y via the Z pathway in human cells.”然后再提出你的研究假设,当理论依据清晰时,可以用如 “We hypothesized that inhibiting X would rescue Y deficiency in this model.”的句子点出研究目标。要求:1)明确陈述本研究拟解决的核心科学问题;

智能AI让学术科研更容易 181

最短路径算法-----Dijkstra迪杰斯特拉算法

最近巩固一下算法,提高自己内力,网上看到查看到这篇介绍很详细的《Dijkstra迪杰斯特拉算法》,在这里转载记录一下。 前言 本章介绍迪杰斯特拉算法。和以往一样,本文会先对迪杰斯特拉算法的理论论知识进行介绍,然后给出C语言的实现。后续再分别给出C++和Java版本的实现。 目录1.迪杰斯特拉算法介绍2.迪杰斯特拉算法图解3.迪杰斯特拉算法的代码说明4.迪杰斯特拉算法的源码 转载请注明出处:http://www.cnblogs.com/skywang12345/ 更多内容:数据结构与算法系...

zzhongcy的专栏 1万+

SAP UI5 应用开发教程的学习目录 - 循序渐进,由浅入深,适合不同水平的 SAP UI5 开发人员

笔者在业余时间设计了这份 SAP UI5 学习教程,把开发一个完整的 SAP UI5 应用的流程,拆分成若干个步骤,力求每个步骤里,把涉及到的知识点都涵盖到。建议零基础或者对 SAP UI5 知之甚少的 SAP UI5 初学者,按照顺序从第一个步骤开始循序渐进地学习,把这些代码下载到本地,配合教程的文字讲解,自己动手,以加深理解。 对于已经有一定基础的 SAPUI5 开发人员,可以通过本文的目录,跳过前面一部分比较基础的章节,直接挑选对自己工作项目有关,或者包含某些知识点自己尚未掌握的章节来有针对性地学习。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 3万+

单源最短路径之迪杰斯特拉算法(C语言)

Dijkstra(迪杰斯特拉)算法 采用广度优先搜索思想,对有向赋权图寻找最短路径。 该算法对于不含负权的有向图来说,是目前已知的最快的单源最短路径算法。 时间复杂度:O(n^2) 基本原理:不断为为每个顶点 v 保留目前为止所找到的从s到v的最短路径 上图为戴克斯特拉算法应用示意图。 起点以左下角的红点,目标是右上角的绿点,中间灰色的倒L型为障碍物。蓝色空圈表示”暂定”,用以搜索...

一串代码的博客 8275

[EdgeAI] NXP eIQ 机器学习Toolkit (二):模型篇

eIQ™机器学习(ML)软件开发环境支持在恩智浦EdgeVerse™微控制器和微处理器(包括i.MX RT跨界MCU和i.MX系列应用处理器)上使用ML算法。eIQ ML软件包括称为eIQ Toolkit的ML工作流工具以及推理引擎、神经网络编译器和优化库。......

HowieXue 薛永浩的博客 1万+

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

校园导游系统_C语言实现_Dijkstra(迪杰斯特拉算法)_数据结构

西京学院导游系统 摘要   要完成对整个导游图系统的功能实现,需要对每一项功能都有清楚的设想和认识,了解并明确每一项功能的实现和需要解决的问题,选择正确并且高效的算法把问题逐个解决,最终实现程序的正确调试运行,有以下设计思路: (1)结合本校的实际情况,选出17个景点; (2)人为手工为选出17个景点赋上相关信息(名称、代号、简介信息、以及路径等等); (3)根据选出来的17个景点用邻接矩阵存储西京校园图。 (4)依照景点的相关信息创建西京校园图。 (5)把纸质上的内容,利用Devcpp编程语言编写查找景点

贰十二的博客 5418

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

【数据结构】图论中求最短路径——迪杰斯特拉算法(Dijkstra)、弗洛伊德算法(Floyd)

数据结构——图论中求最短路径——迪杰斯特拉算法(Dijkstra)、弗洛伊德算法(Floyd)

ChenZHIHAO_y的博客 6901

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

基于C语言利用迪杰斯特拉(Dijkstra)实现自定义城市最短路径

利用迪杰斯特拉(Dijkstra)算法求得最短路径

WayneLJL的博客 2771

【迪杰斯特拉算法】单源最短路径(C语言实现)

迪杰斯特拉算法 迪杰斯特拉算法(Dijkstra)是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。 C语言代码实现如下: //【贪心算法】单源最短路径问题 #include <stdio.h> #include <stdlib.h> #define N 5 // 5个顶点,1、2、3、4、5 #define M 9999 /.

qq_54623898的博客 3106

数据结构_单源最短路径Dijkstra算法(C语言)

图的应用 单源最短路径迪杰斯特拉(Dijkstra)算法 2. 源代码: 3. 测试: 测试环境 : Windows 10 编译软件 : Visual C++ 6.0 测试用例:

小黯 5152

c语言写邻接矩阵的最短路径的Dijkstra算法(附有详细代码)

(1) 用dis数组来存储源点1到其他顶点的初始路径,标记1号顶点,此时dis数组中的值称为最短路径的估计值。(2) 从dis数组中找出离源起点最近的点2号,以2号顶点为源点进行找最近的顶点。把2号顶点标记,表示已经有最小值。

qq_62185081的博客 2944
上一篇: 欧姆龙CJ1W-SCU串口网关实战:如何通过中继PLC实现远程在线编程(附完整配置截图)
下一篇: Excel表格秒变Ren‘Py游戏脚本:零代码制作视觉小说的神奇工具全解析
github5actions
博客等级 码龄1天 0粉丝 1824原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值