news 2026/5/16 15:30:05

【附C源码】基于邻接表的图结构实现与算法实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【附C源码】基于邻接表的图结构实现与算法实践

【附C源码】基于邻接表的图结构实现与算法实践

图(Graph)作为非线性数据结构的核心成员,在社交网络分析、路径规划、依赖管理等领域有着广泛应用。本文将介绍一种基于邻接表的图结构C语言实现,涵盖基础操作、遍历算法以及最短路径求解。

一、存储结构设计

邻接表是图的一种链式存储结构,由顶点数组和边链表组成。对于稀疏图而言,相较于邻接矩阵,邻接表能够显著降低空间复杂度。

1.1 数据结构定义

// 边节点typedefstructEdgeNode{intdest;// 目标顶点索引intweight;// 边权重structEdgeNode*next;// 下一条边}EdgeNode;// 顶点节点typedefstructVertex{intdata;// 顶点数据EdgeNode*firstEdge;// 邻接边链表头指针}Vertex;// 图结构typedefstruct{Vertex*vertices;// 顶点数组intvertexCount;// 当前顶点数intcapacity;// 容量上限bool isDirected;// 有向/无向标识}Graph;

上述设计中,EdgeNode采用头插法构建链表,这使得边插入操作的时间复杂度为O(1)。isDirected字段用于区分有向图与无向图,后者在添加边时需要同时维护双向关系。

1.2 内存布局示意

Vertex1

Vertex0

Graph

vertices指针

Vertex数组

vertexCount

capacity

isDirected

data=0

EdgeNode
dest=1
weight=10

EdgeNode
dest=2
weight=5

NULL

data=1

EdgeNode
dest=2
weight=2

NULL

二、核心操作实现

2.1 图的创建与销毁

图的初始化需要分配顶点数组空间,并将各顶点的边链表置空:

Graph*graphCreate(intcapacity,bool isDirected){Graph*g=(Graph*)malloc(sizeof(Graph));g->vertices=(Vertex*)malloc(sizeof(Vertex)*capacity);for(inti=0;i<capacity;i++){g->vertices[i].firstEdge=NULL;}g->vertexCount=0;g->capacity=capacity;g->isDirected=isDirected;returng;}

销毁操作需遍历所有顶点,释放其边链表节点,最后释放顶点数组和图结构本身。此处需注意避免内存泄漏,尤其是边节点的逐个释放。

2.2 边的添加与删除

无向图的边添加需要维护两个方向的链接:

boolgraphAddEdge(Graph*g,intfrom,intto,intweight){// 创建正向边EdgeNode*newEdge=(EdgeNode*)malloc(sizeof(EdgeNode));newEdge->dest=to;newEdge->weight=weight;newEdge->next=g->vertices[from].firstEdge;g->vertices[from].firstEdge=newEdge;// 无向图需添加反向边if(!g->isDirected){EdgeNode*reverseEdge=(EdgeNode*)malloc(sizeof(EdgeNode));reverseEdge->dest=from;reverseEdge->weight=weight;reverseEdge->next=g->vertices[to].firstEdge;g->vertices[to].firstEdge=reverseEdge;}returntrue;}

边删除操作相对复杂,需要遍历链表定位目标节点,并正确处理头节点的特殊情况。无向图的边删除同样需要双向处理。

2.3 顶点删除

顶点删除是图操作中最复杂的部分,涉及以下步骤:

  1. 删除该顶点的所有出边
  2. 遍历其他顶点,删除指向该顶点的入边
  3. 调整受影响的边目标索引(因为顶点数组发生移动)
  4. 压缩顶点数组

开始删除顶点v

删除v的所有出边

遍历其他顶点

存在指向v的边?

删除该边

目标索引>v?

目标索引减1

继续遍历

顶点数组前移

vertexCount减1

该操作的时间复杂度为O(V + E),其中V为顶点数,E为边数。

三、图遍历算法

3.1 深度优先搜索(DFS)

DFS采用递归实现,沿着一条路径尽可能深入,直至无法继续后回溯:

voidgraphDFSHelper(Graph*g,intindex,bool*visited){visited[index]=true;printf("%d ",g->vertices[index].data);EdgeNode*edge=g->vertices[index].firstEdge;while(edge){if(!visited[edge->dest]){graphDFSHelper(g,edge->dest,visited);}edge=edge->next;}}

DFS的时间复杂度为O(V + E),空间复杂度取决于递归深度,最坏情况下为O(V)。

3.2 广度优先搜索(BFS)

BFS借助队列实现,按层次遍历图的节点:

voidgraphBFS(Graph*g,intstartIndex){bool*visited=(bool*)calloc(g->vertexCount,sizeof(bool));Queue*q=queueCreate(g->vertexCount+1);visited[startIndex]=true;queueEnqueue(q,startIndex);while(!queueIsEmpty(q)){intindex;queueDequeue(q,&index);printf("%d ",g->vertices[index].data);// 将未访问的邻接顶点入队EdgeNode*edge=g->vertices[index].firstEdge;while(edge){if(!visited[edge->dest]){visited[edge->dest]=true;queueEnqueue(q,edge->dest);}edge=edge->next;}}}

BFS同样具有O(V + E)的时间复杂度,但空间复杂度为O(V),用于维护队列和访问标记数组。

四、最短路径算法

4.1 Dijkstra算法实现

Dijkstra算法用于求解单源最短路径,要求图中不存在负权边。本实现采用简单数组选择最小距离顶点,时间复杂度为O(V²)。

voidgraphDijkstra(Graph*g,intstartIndex,int*dist,int*prev){bool*visited=(bool*)calloc(g->vertexCount,sizeof(bool));// 初始化for(inti=0;i<g->vertexCount;i++){dist[i]=INT_MAX;prev[i]=-1;}dist[startIndex]=0;for(inti=0;i<g->vertexCount;i++){// 选择未访问的最小距离顶点intminDist=INT_MAX,u=-1;for(intj=0;j<g->vertexCount;j++){if(!visited[j]&&dist[j]<minDist){minDist=dist[j];u=j;}}if(u==-1)break;// 剩余顶点不可达visited[u]=true;// 松弛操作EdgeNode*edge=g->vertices[u].firstEdge;while(edge){intv=edge->dest;if(!visited[v]&&dist[u]!=INT_MAX&&dist[u]+edge->weight<dist[v]){dist[v]=dist[u]+edge->weight;prev[v]=u;// 记录前驱节点}edge=edge->next;}}}

4.2 路径回溯

通过prev数组记录的前驱信息,可以重构最短路径:

// 从终点反向追踪至起点intpath[10],pathLen=0;intcurr=target;while(curr!=-1){path[pathLen++]=curr;curr=prev[curr];}// 逆序输出即为正向路径for(intj=pathLen-1;j>=0;j--){printf("%d ",path[j]);}

五、测试验证

测试用例设计涵盖无向图、有向带权图两种场景,验证内容包括:

  • 顶点与边的增删操作
  • DFS与BFS遍历的正确性
  • Dijkstra算法最短路径计算

测试输出示例:

【有向带权图测试】 图结构 (有向图, 5个顶点): 顶点[0](数据=0): -> [2](权重=5) -> [1](权重=10) 顶点[1](数据=1): -> [3](权重=1) -> [2](权重=2) 顶点[2](数据=2): -> [4](权重=2) -> [3](权重=9) -> [1](权重=3) 顶点[3](数据=3): -> [4](权重=4) 顶点[4](数据=4): -> [3](权重=6) 从顶点0到各顶点的最短路径: 到顶点[0]: 距离=0, 路径=0 到顶点[1]: 距离=8, 路径=0 2 1 到顶点[2]: 距离=5, 路径=0 2 到顶点[3]: 距离=9, 路径=0 2 1 3 到顶点[4]: 距离=7, 路径=0 2 4

从结果可见,Dijkstra算法正确计算了从顶点0到其余各顶点的最短距离及路径。值得注意的是,到顶点1的最短路径并非直接边(权重10),而是经由顶点2中转(5+3=8),算法正确识别了这一更优路径。

六、总结

本文介绍的图实现具备以下特点:

  1. 存储效率:邻接表结构适用于稀疏图场景,空间复杂度为O(V + E)
  2. 功能完整:支持有向/无向、带权/非带权图,提供增删改查全套操作
  3. 算法覆盖:实现了DFS、BFS两种遍历算法以及Dijkstra最短路径算法

后续可扩展的方向包括:使用优先队列优化Dijkstra算法至O((V+E)logV)、增加拓扑排序、强连通分量检测等进阶算法。


⚠️源码地址:https://github.com/anjuxi/C-graph

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/16 15:30:04

LVGL8滚动布局避坑指南:从官方例程到自定义网格(Grid)的完整配置流程

LVGL8滚动布局避坑指南&#xff1a;从官方例程到自定义网格的完整配置流程 第一次接触LVGL8的滚动布局时&#xff0c;我像大多数开发者一样&#xff0c;直接从官方文档复制了示例代码。但当我试图修改成自己的网格布局时&#xff0c;却发现图片错位、滚动失效、事件响应异常等问…

作者头像 李华
网站建设 2026/5/16 15:27:18

Linux开机启动项检查与优化

Linux开机启动项检查与优化Linux 系统启动后会自动拉起大量服务&#xff0c;其中有些是必要基础组件&#xff0c;有些则可能早已不再需要。启动项过多不仅会拉长开机时间&#xff0c;还可能增加资源消耗和攻击面。中级阶段需要掌握的&#xff0c;不只是会开启或关闭某个服务&am…

作者头像 李华
网站建设 2026/5/16 15:27:16

Linux文件传输与远程同步实践

Linux文件传输与远程同步实践在 Linux 环境中&#xff0c;文件传输是极高频操作。配置下发、日志取证、数据迁移、备份同步和跨主机分发&#xff0c;都离不开稳定可靠的传输方式。中级阶段不应只满足于“文件拷过去了”&#xff0c;而要关心传输是否可验证、是否增量、是否安全…

作者头像 李华
网站建设 2026/5/16 15:26:28

2025届必备的十大降AI率助手解析与推荐

Ai论文网站排名&#xff08;开题报告、文献综述、降aigc率、降重综合对比&#xff09; TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 在当下学术研究跟学位论文撰写愈发趋向规范化的这种背景情形之中&#xff0c;选题以及开题报…

作者头像 李华
网站建设 2026/5/16 15:22:14

Hermit-rs安全机制解析:Rust所有权模型如何保障unikernel安全

Hermit-rs安全机制解析&#xff1a;Rust所有权模型如何保障unikernel安全 【免费下载链接】hermit-rs Hermit for Rust. 项目地址: https://gitcode.com/gh_mirrors/he/hermit-rs 在 unikernel 领域&#xff0c;安全始终是开发者关注的核心议题。Hermit-rs 作为基于 Rus…

作者头像 李华
网站建设 2026/5/16 15:22:13

3个VPS运维困境:reinstall一键重装工具如何重塑系统管理体验

3个VPS运维困境&#xff1a;reinstall一键重装工具如何重塑系统管理体验 【免费下载链接】reinstall 一键DD/重装脚本 (One-click reinstall OS on VPS) 项目地址: https://gitcode.com/GitHub_Trending/re/reinstall 你是否经历过这样的场景&#xff1a;凌晨三点被告警…

作者头像 李华