图——数据结构 逆邻接表与十字链表
前言:如果你已经学习了邻接表的存储思想,那么逆邻接表也非常好理解,我们的重点是十字链表
首先我们来继续介绍逆邻接表,逆邻接表和邻接表是一样的,只不过在邻接表上,一个顶点后面连接的一串节点都是以顶点为弧尾的弧头节点,我们建立邻接表的时候就先查找一条边的起点,然后往这个起点上连接新的顶点,那么逆邻接表就是反过来,逆邻接表中一个顶点后面的一串节点都是以顶点为弧头的弧尾节点,我们建立逆邻接表的时候,先查找一条边的终点,然后往这个重点上连接包含起点信息的节点;
逆邻接表就介绍到这里,下面我们介绍十字链表,首先我们抛出这样一句话,十字链表是正邻接表和逆邻接表的合体,然后我们再介绍这样一个观念,我们在逆邻接表或者邻接表中所提到的节点,其实可以理解为弧节点,弧节点可以包含弧头顶点在图中的位置(我们在正邻接表中遇到的),也可以包含弧尾顶点在图中的位置(逆邻接表);下面我们给出具体的例子帮助大家理解
现在思考下当我们要建立十字链表时会发生些什么:首先我们建立好顶点数组,和在邻接表中的一样,然后呢,我们开始添加 e01 这个边,或者说弧现在我们要用弧节点的思路去理解他,如何添加这个弧,这个弧包含了哪些信息?弧包含了tail,代表弧尾表示的顶点在图中的位置,也就是这个弧是由谁发射出来的,head呢,表示这个弧指向了谁,同样储存的是顶点在顶点数组中的索引;后面这两项是地址,是什么类型的地址呢,这个地址指向的空间要放什么东西呢,放的仍然是弧节点,但是这两个地址所放的不是同一个弧节点,这个tlink,指向的是下一个弧节点,tlink指向的这个弧节点的弧尾和tlink所在的弧的弧尾是相同的,按照这样的想法,我们很容易想到沿着tlink一路走下去,我们就可以遍历由同一个顶点发射的所有弧,相当于是一个正邻接表,同理沿着hlink我们会遍历指向同一个顶点的所有弧,而一个弧节点既有hlink,又有tlink,所以正邻接表和逆邻接表是交叉的,这就叫做十字链表,
那么如何来具体的建立一个十字链表呢,回顾上一节我们提到的,先建立顶点数组,然后按照用户输入的边去建立弧节点,然后给弧节点填充相应的信息,再把弧节点链接到已有的十字链表上,链接的时候又需要分情况,我这个弧节点是不是处于邻接表的第一个或者是逆邻接表的第一个,如果是的话,需要操作顶点的firstin或者firstout,如果不是的话需要操作”最后一个“弧节点,那么这里和上一节的操作一样,我们仍然需要去标记一下”最后一个弧节点“,只不过这里不仅要标记正邻接表的最后一个弧节点,还需要标记逆邻接表中处于最后一个的弧节点,
如此如此这番这番,思路就跃然纸上了(具体化了),下面我们给出具体代码,并针对上一节中,判定是否一个顶点没有弧节点相连(十字链表中其实是没有正邻接表‘也就是弧尾’或逆邻接表‘也就是弧头’相连,或者两个都没有)的判定进行了更正;
1 //验证图的十字链表存储 2 #include <stdio.h> 3 #include <windows.h> 4 #define vexNum 10 5 6 typedef struct ArcNode 7 { 8 //索引 9 int tailVex; 10 int headVex; 11 12 //地址,按照tlink,可以一路遍历完正邻接表 13 struct ArcNode *tlink; 14 struct ArcNode *hlink; 15 16 //info代表弧的权重 17 int info; 18 19 } ArcNode; 20 21 typedef struct VexNode 22 { 23 char data; 24 ArcNode *last1End; //正邻接表的最后一个弧节点 25 ArcNode *last2End; //逆邻接表的最后一个弧节点 26 ArcNode *second1; //正、第一个弧节点 27 ArcNode *second2; //逆、第一个弧节点 28 } VexNode; 29 30 typedef struct graph 31 { 32 int vexN; 33 int edgeN; 34 VexNode adjList[vexNum]; 35 } graph; 36 37 //寻找顶点在图中的位置 38 int locatVex(char vex, graph g) 39 { 40 int i = 0; 41 for (i; i < g.vexN; i++) 42 { 43 if (vex == g.adjList[i].data) 44 { 45 return i; 46 } 47 } 48 return -1; 49 } 50 void creatOLGraph(graph *g) 51 { 52 printf("请输入节点数和边数:\n"); 53 scanf("%d %d", &g->vexN, &g->edgeN); 54 getchar(); 55 printf("请输入节点数组,不带空格\n"); 56 int i = 0; 57 for (i; i < g->vexN; i++) 58 { 59 scanf("%c", &g->adjList[i].data); 60 g->adjList[i].second1 = NULL; 61 g->adjList[i].second2 = NULL; 62 g->adjList[i].last1End = NULL; 63 g->adjList[i].last2End = NULL; 64 } 65 getchar(); 66 printf("请输入边:\n"); 67 68 int v1, v2; 69 char vv1, vv2; 70 for (i = 0; i < g->edgeN; i++) 71 { 72 scanf("%c %c", &vv1, &vv2); 73 getchar(); 74 v1 = locatVex(vv1, *g); 75 v2 = locatVex(vv2, *g); 76 ArcNode *p; 77 p = (ArcNode *)malloc(sizeof(ArcNode)); 78 79 //同一个弧节点,先对正邻接表操作,再对逆邻接表操作 80 81 //弧节点的弧尾是v1 82 p->tailVex = v1; 83 if (g->adjList[v1].last1End == NULL) 84 { 85 //顶点的第一个弧节点是p,该顶点正邻接表的最后一个弧节点也是p 86 g->adjList[v1].second1 = p; 87 g->adjList[v1].last1End = p; 88 } 89 else 90 { 91 //向正邻接表中添加一个元素, 92 g->adjList[v1].last1End->tlink = p; 93 g->adjList[v1].last1End = p; 94 } 95 96 p->headVex = v2; 97 if (g->adjList[v2].last2End == NULL) 98 { 99 g->adjList[v2].second2 = p; 100 g->adjList[v2].last2End = p; 101 } 102 else 103 { 104 g->adjList[v2].last1End->hlink = p; 105 g->adjList[v2].last1End = p; 106 } 107 } 108 } 109 110 int main() 111 { 112 graph g; 113 creatOLGraph(&g); 114 //验证十字链表的存储 115 int i = 0; 116 for (i; i < g.vexN; i++) 117 { 118 ArcNode *show; 119 //之前我们做判断,是判断“最后一个节点”是否为顶点,现在我们更改了初始化条件,把之前的初始化为顶点地址改成了初始化为NULL这就避免了类型不兼容的警告 120 if (g.adjList[i].last2End != NULL) 121 { 122 printf("发往%c的弧,弧尾是 ", g.adjList[i].data); 123 show = g.adjList[i].second2; 124 while (show != g.adjList[i].last2End) 125 { 126 printf("%c、", g.adjList[show->tailVex].data); 127 show = show->hlink; 128 } 129 printf("%c\n", g.adjList[show->tailVex].data); 130 } 131 132 if (g.adjList[i].last1End != NULL) 133 { 134 printf("由%c发出的弧的弧头是 ", g.adjList[i].data); 135 show = g.adjList[i].second1; 136 while (show != g.adjList[i].last1End) 137 { 138 printf("%c、", g.adjList[show->headVex].data); 139 show = show->tlink; 140 } 141 printf("%c\n", g.adjList[show->headVex].data); 142 } 143 } 144 system("pause"); 145 return 0; 146 }
参考:《新编数据结构案例教程(C/C++)版》薛晓亚主编
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!