【数据结构——十字链表】
引入
在邻接矩阵之后,为了提升空间利用率而引出了邻接表。邻接表适用于计算有向图中顶点的出度数、逆邻接表适用于计算有向图中顶点的入度数。十字链表可以同时满足这两个要求。
十字链表是一种用于表示有向图的数据结构,它的核心特点是将“以弧尾为线索的链表”和“以弧头为线索的链表”交叉结合:
- 每条弧(有向边)同时属于两个链表:一个是以其弧尾顶点为起点的“出边链表”,另一个是以其弧头顶点为终点的“入边链表”。
- 这种“交叉链表”的结构,让它能高效地处理有向图的入度、出度查询,以及弧的增删操作,因此命名为 CrossLinkGraph (十字链接图)。
十字链表的优缺点
- 优点:
适合稀疏矩阵的高效存储,节省空间。
支持快速按行或按列遍历非零元素。 - 缺点:
实现复杂度较高,需维护双向链表。
随机访问元素性能较差,需遍历链表
结构示图
tNext表示的是除head之外以tail为弧尾的弧头节点。
同样,hNext表示的是除tail之外以head为弧头的弧尾节点。
例图:
对应结构模型:
每个弧(有向边)用一个 ArcBox 节点表示,每个顶点用包含 firstIn (入度链头)和 firstOut (出度链头)的节点表示。
上图结构模型中的实线代表出边(从当前顶点指出去,即当前节点做为弧尾),虚线代表入边(从其他节点指向当前顶点,即当前节点做为弧头)。
--------------------------------------------------------------------------------------------------------------
例如(为方便观察下面贴的图片是重复的):
-
实线部分
v0的节点结构中firstOut指向的边结构是以v0为弧尾的,以v0为弧尾的节点是v3,所以边结构中head为v3,tNext表示除v3之外仍然以v0为弧尾的节点,显然没有了,所以只能为空。
-
虚线部分
v0的firstIn就是当v0作为弧头被其他节点指向时的数据,它表示的是指向v0的节点,也就是以v0为弧头的弧尾节点。v1,v2就是这样的节点,按次序进行指向,所以v0的firstIn就指向以v1为弧尾的弧头位置(还是v0)。而hNext就是指向除v1之外且以v0作为弧头的弧尾节点的边结构,这里是v0与v2节点之间的,则hNext就指向以v0作为弧头,v2为弧尾的边结构的hNext(方便再次寻找以v0作为弧头的弧尾节点的其他节点)。找到这里对于v0就没有满足条件的节点了,所以hNext指向空。
注意:在十字链表中,同一条弧(有向边)会同时出现在“出边链表”和“入边链表”中:
- 从弧尾顶点的角度,这条弧是“出边”,存储在 firstOut 链表中;
- 从弧头顶点的角度,这条弧是“入边”,存储在 firstIn 链表中。
但本质上,这是同一块内存( ArcBox 结构体)被两个链表指针同时引用。因此 releaseCrossGraph只通过“出边/或入边链表”遍历(下面采用的是出边遍历)并释放所有弧的内存,同时保证了内存的不重复释放。
头文件
#pragma once
//十字链表的边结构
typedef struct arcBox{int tailVertex;struct arcBox* tailNext;int headVertex;struct arcBox* headNext;int weight;
}ArcBox;//十字链表的顶点结构
typedef struct {int no;const char* show;ArcBox* firstIn; //该节点的入度ArcBox* firstOut; //该节点的出度
}CrossVertex;//利用十字链表的结构实现图结构
typedef struct {CrossVertex* nodes;int numVertex;int numEdge;
}CrossGraph;CrossGraph* createCrossGraph(int n);
void releaseCrossGraph(CrossGraph* graph);void initCrossGraph(CrossGraph* graph, const char* names[], int num);
void addCrossArc(CrossGraph* graph, int tail, int head, int w);//计算编号为no的节点的入度
int inDegreeCrossGraph(const CrossGraph* graph, int no);
//计算编号为no的节点的出度
int outDegreeCrossGraph(const CrossGraph* graph, int no);
功能实现
创建
CrossGraph* createCrossGraph(int n) {CrossGraph* graph = (CrossGraph*)malloc(sizeof(CrossGraph));if (graph == NULL)return NULL;graph->nodes = (CrossVertex*)malloc(sizeof(CrossVertex)*n);if (graph->nodes == NULL){free(graph);return NULL;}graph->numEdge = 0;graph->numVertex = n;return graph;
}
释放
void releaseCrossGraph(CrossGraph* graph) {int numEdges = 0; //依次释放:边,节点,图if (graph) {if (graph->nodes) {for (int i = 0; i < graph->numVertex; i++) {ArcBox* box = graph->nodes[i].firstOut;ArcBox* tmp;while (box) {tmp = box;box = box->tailNext;free(tmp);numEdges++;}}printf("released %d edges\n", numEdges);free(graph->nodes);}free(graph);}
}
初始化
void initCrossGraph(CrossGraph* graph, const char* names[], int num) {for (int i = 0; i < num; ++i) {graph->nodes[i].no = i;graph->nodes[i].show = names[i];graph->nodes[i].firstIn = graph->nodes[i].firstOut = NULL;}
}
添加边
//添加一条以tail为弧尾,以head为弧头的边
void addCrossArc(CrossGraph* graph, int tail, int head, int w) {ArcBox* box = (ArcBox*)malloc(sizeof(ArcBox));if (box==NULL)return ;box->weight = w;//从出度关系上进行插入(头插法)box->tailVertex = tail;box->tailNext = graph->nodes[tail].firstOut;graph->nodes[tail].firstOut = box;//从入度关系上进行插入(头插法)box->headVertex = head;box->headNext = graph->nodes[head].firstIn;graph->nodes[head].firstIn = box;
}
计算入度
int inDegreeCrossGraph(const CrossGraph* graph, int no)
{int count = 0;ArcBox* box = graph->nodes[no].firstIn;while (box!=NULL) {count++;box = box->headNext;}return count;
计算出度
int outDegreeCrossGraph(const CrossGraph* graph, int no) {int count = 0;ArcBox* box = graph->nodes[no].firstOut;while (box) {count++;box = box->tailNext;}return count;
}
功能调用
void setupCrossGraph(CrossGraph* graph) {const char* nodeName[] = {"v0","v1","v2","v3"};initCrossGraph(graph, nodeName, 4);addCrossArc(graph, 0, 3, 1);addCrossArc(graph, 1, 0, 1);addCrossArc(graph, 1, 2, 1);addCrossArc(graph, 2, 0, 1);addCrossArc(graph, 2, 1, 1);
}int main() {int n = 4;CrossGraph* graph = createCrossGraph(n);if (graph == NULL) {return -1;}setupCrossGraph(graph);printf("V0的入度:%d\n", inDegreeCrossGraph(graph, 2));printf("V0的出度:%d\n", outDegreeCrossGraph(graph, 2));releaseCrossGraph(graph);return 0;
}