图——数据结构 逆邻接表与十字链表

前言:如果你已经学习了邻接表的存储思想,那么逆邻接表也非常好理解,我们的重点是十字链表

  首先我们来继续介绍逆邻接表,逆邻接表和邻接表是一样的,只不过在邻接表上,一个顶点后面连接的一串节点都是以顶点为弧尾的弧头节点,我们建立邻接表的时候就先查找一条边的起点,然后往这个起点上连接新的顶点,那么逆邻接表就是反过来,逆邻接表中一个顶点后面的一串节点都是以顶点为弧头的弧尾节点,我们建立逆邻接表的时候,先查找一条边的终点,然后往这个重点上连接包含起点信息的节点;

  逆邻接表就介绍到这里,下面我们介绍十字链表,首先我们抛出这样一句话,十字链表是正邻接表和逆邻接表的合体,然后我们再介绍这样一个观念,我们在逆邻接表或者邻接表中所提到的节点,其实可以理解为弧节点,弧节点可以包含弧头顶点在图中的位置(我们在正邻接表中遇到的),也可以包含弧尾顶点在图中的位置(逆邻接表);下面我们给出具体的例子帮助大家理解

  现在思考下当我们要建立十字链表时会发生些什么:首先我们建立好顶点数组,和在邻接表中的一样,然后呢,我们开始添加 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++)版》薛晓亚主编

 

posted @   dou_fu_gan  阅读(1738)  评论(0编辑  收藏  举报
编辑推荐:
· 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框架的用法!
点击右上角即可分享
微信分享提示