【数据结构——十字链表】

引入

在邻接矩阵之后,为了提升空间利用率而引出了邻接表。邻接表适用于计算有向图中顶点的出度数、逆邻接表适用于计算有向图中顶点的入度数。十字链表可以同时满足这两个要求。

十字链表是一种用于表示有向图的数据结构,它的核心特点是将"以弧尾为线索的链表"和"以弧头为线索的链表"交叉结合:

  • 每条弧(有向边)同时属于两个链表:一个是以其弧尾顶点为起点 的"出边链表",另一个是以其弧头顶点为终点 的"入边链表"。
  • 这种"交叉链表"的结构,让它能高效地处理有向图的入度、出度查询,以及弧的增删操作,因此命名为 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只通过"出边/或入边链表"遍历(下面采用的是出边遍历)并释放所有弧的内存,同时保证了内存的不重复释放。

头文件

c 复制代码
#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);

功能实现

创建
c 复制代码
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;
}
释放
c 复制代码
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);
	}
}
初始化
c 复制代码
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;
	}
}
添加边
c 复制代码
//添加一条以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;
}
计算入度
c 复制代码
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;
计算出度
c 复制代码
int outDegreeCrossGraph(const CrossGraph* graph, int no) {
	int count = 0;
	ArcBox* box = graph->nodes[no].firstOut;
	while (box) {
		count++;
		box = box->tailNext;
	}
	return count;
}

功能调用

c 复制代码
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;
}
相关推荐
Yvonne爱编码2 小时前
机器人的“神经网络”:以太网技术如何重塑机器人内部通信?【技术类】
网络·机器人
自我陶醉@2 小时前
计算机网络---传输层
网络·计算机网络·考研·学习方法·408
三毛20043 小时前
玳瑁的嵌入式日记---0928(ARM--UART)
网络·arm开发
比特森林探险记3 小时前
Golang GMP 模型深度解析
网络·算法·golang
_清浅3 小时前
计算机网络【第二章-物理层】
服务器·网络·计算机网络
init_23613 小时前
IS-IS 与 OSPF 路由汇总机制:边界、应用与核心差异深度分析报告
网络·智能路由器·php
1688red3 小时前
实现VLAN间通信
网络
chem41113 小时前
协议 NTP UDP 获取实时网络时间
网络·网络协议·udp
歪歪1003 小时前
如何在项目中选择使用HTTP还是WebSocket?
网络·websocket·网络协议·计算机网络·http·网络安全