【数据结构第 6 章 ③】- 用 C 语言实现邻接表并简单介绍十字链表和邻接多重表

news2024/11/29 8:54:37

目录

一、邻接表

1.1 - ALGraph.h

1.2 - ALGraph.c

1.3 - Test.c

二、十字链表

三、邻接多重表


 


一、邻接表

邻接表(Adjacency List)是图的一种链式存储结构。在邻接表中,对图中每个顶点建立一个单链表,第 i 个单链表中的结点表示依附于顶点 vi 的边(对有向图是以顶点 vi 为尾的弧)。每个结点由 3 个域组成,其中邻接顶点域(adjVexPos)指示与顶点 vi 邻接的顶点在图中的位置,链域(nextEdge)指示下一条边(或弧)的结点;数据域(info)存储和边(或弧)相关的信息,如权值等

在表头结点中,除了设有链域(firstEdge)指向链表中的第一个结点之外,还设有存储顶点 vi 的名称或其他有关信息的数据域(vertex)。这些表头结点通常以顺序结构的形式存储,以便随机访问任一顶点的边链表。

下图 (a) 和 (b) 所示为图一中 G1 和 G2 的邻接表。

在无向图的邻接表中,顶点 vi 的度恰为第 i 个链表中的结点数;而在有向图中,第 i 个链表中的结点个数只是顶点 vi 的出度,为求入度,必须遍历整个邻接表。在所有链表中,其邻接顶点域的值为 i 的结点个数是顶点 vi 的入度。有时,为了便于确定顶点的入度,可以建立一个有向图的逆邻接表,即对每个顶点 vi 建立一个链接所有进入 vi 的边的表。

下图 (c) 所示为图一中有向图 G1 的逆邻接表。

1.1 - ALGraph.h

注意:下面是以无向图为例的

#pragma once

#define DEFAULT_CAPACITY 10

typedef char VertexType;

typedef struct EdgeNode
{
	int adjVexPos;
	struct EdgeNode* nextEdge;
}EdgeNode;

typedef struct VertexNode
{
	VertexType vertex;
	EdgeNode* firstEdge;
}VertexNode;

typedef struct ALGraph
{
	VertexNode* vertices;
	int vSize;
	int eSize;
	int capacity;
}ALGraph;

// 基本操作
void ALGraphInit(ALGraph* pg);  // 初始化

void ShowAdjList(ALGraph* pg);  // 显示邻接表

int GetVertexPos(ALGraph* pg, VertexType v);  // 获取顶点的位置

void InsertVertex(ALGraph* pg, VertexType v);  // 插入顶点
void InsertEdge(ALGraph* pg, VertexType v1, VertexType v2);  // 插入边

void EraseVertex(ALGraph* pg, VertexType v);  // 删除顶点
void EraseEdge(ALGraph* pg, VertexType v1, VertexType v2);  // 删除边

int GetFirstAdjVexPos(ALGraph* pg, VertexType v);  // 获取 v 的第一个邻接顶点的位置
int GetNextAdjVexPos(ALGraph* pg, VertexType v, VertexType w);
// 获取 v 的(相对于 w)的下一个邻接顶点的位置

void ALGraphDestroy(ALGraph* pg);  // 销毁

1.2 - ALGraph.c

  1. 初始化

    void ALGraphInit(ALGraph* pg)
    {
    	assert(pg);
    	pg->vSize = pg->eSize = 0;
    	pg->capacity = DEFAULT_CAPACITY;
    
    	pg->vertices = (VertexNode*)malloc(sizeof(VertexNode) * pg->capacity);
    	assert(pg->vertices);
    	for (int i = 0; i < pg->capacity; ++i)
    	{
    		pg->vertices[i].firstEdge = NULL;
    	}
    }
  2. 显示邻接表

    void ShowAdjList(ALGraph* pg)
    {
    	assert(pg);
    	for (int i = 0; i < pg->vSize; ++i)
    	{
    		printf("%d %c:>", i, pg->vertices[i].vertex);
    		EdgeNode* cur = pg->vertices[i].firstEdge;
    		while (cur)
    		{
    			printf("%d-->", cur->adjVexPos);
    			cur = cur->nextEdge;
    		}
    		printf("NULL\n");
    	}
    }
  3. 获取顶点的位置

    int GetVertexPos(ALGraph* pg, VertexType v)
    {
    	assert(pg);
    	for (int i = 0; i < pg->vSize; ++i)
    	{
    		if (pg->vertices[i].vertex == v)
    			return i;
    	}
    	return -1;
    }
  4. 插入顶点

    void InsertVertex(ALGraph* pg, VertexType v)
    {
    	assert(pg);
    	// 考虑是否需要扩容
    	if (pg->vSize == pg->capacity)
    	{
    		VertexNode* tmp = (VertexNode*)realloc(pg->vertices, sizeof(VertexNode) * 2 * pg->capacity);
    		assert(tmp);
    		pg->vertices = tmp;
    		for (int i = pg->capacity; i < 2 * pg->capacity; ++i)
    		{
    			pg->vertices[i].firstEdge = NULL;
    		}
    		pg->capacity *= 2;
    	}
    	// 插入顶点
    	pg->vertices[pg->vSize++].vertex = v;
    }
  5. 插入边

    void InsertEdge(ALGraph* pg, VertexType v1, VertexType v2)
    {
    	assert(pg);
    	int pos1 = GetVertexPos(pg, v1);
    	int pos2 = GetVertexPos(pg, v2);
    	if (pos1 == -1 || pos2 == -1)
    		return;
    
    	// 插入 (v1, v2)
    	EdgeNode* p = (EdgeNode*)malloc(sizeof(EdgeNode));
    	assert(p);
    	p->adjVexPos = pos2;
    	// 头插
    	p->nextEdge = pg->vertices[pos1].firstEdge;
    	pg->vertices[pos1].firstEdge = p;
    
    	// 插入 (v2, v1)
    	p = (EdgeNode*)malloc(sizeof(EdgeNode));
    	assert(p);
    	p->adjVexPos = pos1;
    	// 头插
    	p->nextEdge = pg->vertices[pos2].firstEdge;
    	pg->vertices[pos2].firstEdge = p;
    
    	++pg->eSize;  // 注意:边数只需要加 1
    }
  6. 删除顶点

    // 删除顶点
    void EraseVertex(ALGraph* pg, VertexType v)
    {
    	assert(pg);
    	int pos = GetVertexPos(pg, v);
    	if (pos == -1)
    		return;
    
    	// 第一步:删除和顶点 v 相关联的边
    	EdgeNode* cur = pg->vertices[pos].firstEdge;
    	while (cur)
    	{
    		// 找到邻接顶点 v2, 先删除 (v2, v)
    		int pos2 = cur->adjVexPos;
    		EdgeNode* prev2 = NULL;
    		EdgeNode* cur2 = pg->vertices[pos2].firstEdge;
    		while (cur2 && cur2->adjVexPos != pos)
    		{
    			prev2 = cur2;
    			cur2 = cur2->nextEdge;
    		}
    		if (cur2)
    		{
    			if (prev2 == NULL)
    				pg->vertices[pos2].firstEdge = cur2->nextEdge;
    			else
    				prev2->nextEdge = cur2->nextEdge;
    			free(cur2);
    		}
    
    		// 再删除 (v, v2)
    		pg->vertices[pos].firstEdge = cur->nextEdge;
    		free(cur);
    		cur = pg->vertices[pos].firstEdge;
    
    		--pg->eSize;  // 注意不要遗漏
    	}
    
    	// 第二步:删除顶点 v
    	pg->vertices[pos].vertex = pg->vertices[pg->vSize - 1].vertex;
    	pg->vertices[pos].firstEdge = pg->vertices[pg->vSize - 1].firstEdge;
    
    	cur = pg->vertices[pos].firstEdge;
    	while (cur)
    	{
    		int pos2 = cur->adjVexPos;
    		EdgeNode* cur2 = pg->vertices[pos2].firstEdge;
    		while (cur2)
    		{
    			if (cur2->adjVexPos == pg->vSize - 1)
    			{
    				cur2->adjVexPos = pos;
    				break;
    			}
    			cur2 = cur2->nextEdge;
    		}
    		cur = cur->nextEdge;
    	}
    
    	--pg->vSize;  // 注意不要遗漏
    }
  7. 删除边

    void EraseEdge(ALGraph* pg, VertexType v1, VertexType v2)
    {
    	assert(pg);
    	int pos1 = GetVertexPos(pg, v1);
    	int pos2 = GetVertexPos(pg, v2);
    	if (pos1 == -1 || pos2 == -1)
    		return;
    
    	// 删除 (v1, v2)
    	EdgeNode* prev = NULL;
    	EdgeNode* cur = pg->vertices[pos1].firstEdge;
    	while (cur && cur->adjVexPos != pos2)
    	{
    		prev = cur;
    		cur = cur->nextEdge;
    	}
    	if (cur == NULL)  // 说明 (v1, v2) 不存在 
    		return;
    	if (prev == NULL)
    		pg->vertices[pos1].firstEdge = cur->nextEdge;
    	else
    		prev->nextEdge = cur->nextEdge;
    	free(cur);
    
    	// 删除 (v2, v1)
    	// 注意:此时 (v2, v1) 一定存在
    	prev = NULL;
    	cur = pg->vertices[pos2].firstEdge;
    	while (cur->adjVexPos != pos1)
    	{
    		prev = cur;
    		cur = cur->nextEdge;
    	}
    	if (prev == NULL)
    		pg->vertices[pos2].firstEdge = cur->nextEdge;
    	else
    		prev->nextEdge = cur->nextEdge;
    	free(cur);
    
    	--pg->eSize;  // 注意不要遗漏
    }
  8. 获取 v 的第一个邻接顶点的位置

    int GetFirstAdjVexPos(ALGraph* pg, VertexType v)
    {
    	assert(pg);
    	int pos = GetVertexPos(pg, v);
    	if (pos == -1)
    		return -1;
    
    	EdgeNode* cur = pg->vertices[pos].firstEdge;
    	if (cur)
    		return cur->adjVexPos;
    	else
    		return -1;
    }
  9. 获取 v 的(相对于 w)的下一个邻接顶点的位置

    int GetNextAdjVexPos(ALGraph* pg, VertexType v, VertexType w)
    {
    	assert(pg);
    	int pos1 = GetVertexPos(pg, v);
    	int pos2 = GetVertexPos(pg, w);
    	if (pos1 == -1 || pos2 == -1)
    		return -1;
    
    	EdgeNode* cur = pg->vertices[pos1].firstEdge;
    	while (cur && cur->adjVexPos != pos2)
    	{
    		cur = cur->nextEdge;
    	}
    	if (cur && cur->nextEdge)
    		return cur->nextEdge->adjVexPos;
    	else
    		return -1;
    }
  10. 销毁

    void ALGraphDestroy(ALGraph* pg)
    {
    	assert(pg);
    	for (int i = 0; i < pg->vSize; ++i)
    	{
    		EdgeNode* cur = pg->vertices[i].firstEdge;
    		while (cur)
    		{
    			// 头删
    			pg->vertices[i].firstEdge = cur->nextEdge;
    			free(cur);
    			cur = pg->vertices[i].firstEdge;
    		}
    	}
    	free(pg->vertices);
    	pg->vertices = NULL;
    	pg->vSize = pg->eSize = pg->capacity = 0;
    }

1.3 - Test.c

#include "ALGraph.h"
#include <stdio.h>

int main()
{
	ALGraph g;
	ALGraphInit(&g);
	InsertVertex(&g, 'A');
	InsertVertex(&g, 'B');
	InsertVertex(&g, 'C');
	InsertVertex(&g, 'D');
	InsertVertex(&g, 'E');
	InsertEdge(&g, 'A', 'B');
	InsertEdge(&g, 'A', 'D');
	InsertEdge(&g, 'B', 'C');
	InsertEdge(&g, 'B', 'E');
	InsertEdge(&g, 'C', 'D');
	InsertEdge(&g, 'C', 'E');
	ShowAdjList(&g);
	printf("\n");

	EraseVertex(&g, 'C');
	ShowAdjList(&g);
	printf("\n");

	EraseEdge(&g, 'A', 'B');
	ShowAdjList(&g);
	printf("\n");

	printf("%d\n", GetFirstAdjVexPos(&g, 'A'));  // 3
	printf("%d\n", GetNextAdjVexPos(&g, 'A', 'D'));  // -1
	ALGraphDestroy(&g);
	return 0;
}


二、十字链表

十字链表(Orthogonal List)是有向图的另一种链式存储结构。可以看成是将有向图的邻接表和逆邻接表结合起来得到的一种链表。在十字链表中,对应于有向图中的每一条弧有一个结点,对应于每个顶点也有一个结点。这些结点的结构如下图所示。

在弧结点中有 5 个域:其中尾域(tailvex)头域(headvex)分别指示弧尾和弧头这两个顶点在图中的位置,链域 hlink 指向弧头相同的下一条弧,而链域 tlink 指向弧尾相同的下一条弧,info 域指向该弧的相关信息。弧头相同的弧在同一链表上,弧尾相同的弧也在同一链表上

它们的头结点即为顶点结点,它由 3 个域组成:其中 data 域存储和顶点相关的信息,如顶点的名称等;firstinfirstout 为两个链域,分别指向以该顶点为弧头或弧尾的第一个弧结点。

例如下图 (a) 中所示的图的十字链表如下图 (b) 所示。


三、邻接多重表

邻接多重表(Adjacency Multilist)是无向图的另一种链式存储结构。虽然邻接表是无向图的一种有效的存储结构,在邻接表中容易求得顶点和边的各种信息,但是在邻接表中每一条边 有两个结点,分别在第 i 个和第 j 个链表中,这给某些图的操作带来不便,例如在某些图的应用问题中需要对边进行某种操作,如对已被搜索过的边做记号或删除一条边等,此时需要找到表示同一条边的两个结点。因此,在进行这一类操作的无向图的问题中采用邻接多重表更为适宜。

邻接多重表的结构和十字链表类似,在邻接多重表中,每一条边用一个结点表示,它由下图 (a) 所示的 6 个域组成。其中 mark 为标志域,可用以标记该条边是否被搜索过;ivexjvex 为该边依附的两个顶点在图中的位置;ilink 指向下一条依附于顶点 ivex 的边;jlink 指向下一条依附于顶点 jvex 的边,info 为指向和边相关的各种信息的指针域。

每个顶点也用一个结点表示,它由下图 (b) 所示的 2 个域组成。其中,data 域存储和该顶点相关的信息;firstedge 域指示第一条依附于该顶点的边。

例如下图所示为图一中无向图 G2 的邻接多重表。

在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,由于每条边依附于两个顶点,则每个边结点同时链接在两个链表中。可见,对于无向图而言,其邻接多重表和邻接表的差别,仅仅在于同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。因此,除了在边结点中增加一个标志域外,邻接多重表所需的存储量和邻接表相同

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1299990.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用ssm框架进行mapper或者service单纯测试时报错问题处理

问题 使用注解方式配置好ssm后&#xff0c;无法在测试中单独测试mapper层和service层。效果如下&#xff1a; 解决 关闭配置类中的EnableWebMvc 再次运行&#xff0c;即可完成测试。

【WPF 按钮点击后异步上传多文件code示例】

前言: WPF中按钮点击事件如何执行时间太长会导致整个UI线程卡顿&#xff0c;现象就是页面刷新卡住&#xff0c;点击其他按钮无反馈。如下是进行异步执行命令&#xff0c;并远程上传文件的代码。 ![异步上传文件](https://img-blog.csdnimg.cn/direct/20c071929b004dcf9223dee2…

postgresql安装部署(docker版本)

1.在线部署 创建数据库存储目录 mkdir /home/pgdata创建容器 docker run --name postgresql --restartalways -d -p 5432:5432 -v /home/pgdata:/var/lib/postgresql/data --shm-size10g -e POSTGRES_PASSWORD密码 postgis/postgis:12-3.2-alpine–name为设置容器名称 -d表…

第 375 场 LeetCode 周赛题解

A 统计已测试设备 模拟&#xff1a;记录当前已测试设备数量 class Solution { public:int countTestedDevices(vector<int> &batteryPercentages) {int res 0;int s 0;for (auto x: batteryPercentages) {if (x - s > 0) {res;s;}}return res;} };B 双模幂运算 …

NR Channel raster和Sync raster

NR中&#xff0c;由于信道带宽可能非常大&#xff0c;如果UE按照channel Raster进行同步信号搜索&#xff0c;需要的时间很长&#xff0c;且非常耗电&#xff1b;因而NR引入了Synchronization raster的概念&#xff0c;同步信号按照Sync Raster放置。 ARFCN 频点号对应Channel…

java--DateTimeFormatter、Period、Duration

1.DateTimeFormatter 2.LocalDateTime提供的格式化、解析时间的方法 3.Period(一段时期) 可以用于计算两个LocalDate对象相差的年数、月数、天数。 4.Duration(持续时间) 可以用于计算两个时间对象相差的天数、小时数、分数、秒数、纳秒数&#xff1b;支持LocalTime、LocalDa…

【Spring】依赖注入之属性注入详解

前言&#xff1a; 我们在进行web开发时&#xff0c;基本上一个接口对应一个实现类&#xff0c;比如IOrderService接口对应一个OrderServiceImpl实现类&#xff0c;给OrderServiceImpl标注Service注解后&#xff0c;Spring在启动时就会将其注册成bean进行统一管理。在Co…

【小沐学Python】Python实现WebUI网页图表(gradio)

文章目录 1、简介2、安装3、基本测试3.1 入门代码3.2 组件属性3.3 多个输入和输出组件3.4 图像示例3.5 聊天机器人3.6 模块&#xff1a;更灵活、更可控3.7 进度条 结语 1、简介 https://www.gradio.app/ Gradio是用友好的网络界面演示机器学习模型的最快方法&#xff0c;因此…

C# WPF上位机开发(动态库dll的开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多时候&#xff0c;我们并不希望所有的程序都放到一个exe里面。因为这样相当于把所有的风险都放在了一个文件里里面&#xff0c;既不利于程序的升…

漏洞复现-大华dss struts2-045表达式注入漏洞(附漏洞检测脚本)

免责声明 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…

Lambda表达式规则,用法

Lambda表达式是JDK8新增的一种语法格式 1.作用 简化匿名内部类的代码写法 Lambad用法前提&#xff1a;只能简化函数式接口&#xff08;一般加有Funcationallnterface&#xff09;&#xff08;有且仅有一个抽象方法&#xff09;的匿名内部类 匿名内部类&#xff1a;(本质是对…

用23种设计模式打造一个cocos creator的游戏框架----(十二)状态模式

1、模式标准 模式名称&#xff1a;状态模式 模式分类&#xff1a;行为型 模式意图&#xff1a;允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。 结构图&#xff1a; 适用于&#xff1a; 1、一个对象的行为决定于它的状态&#xff0c;并且它必须…

Leetcode—219.存在重复元素II【简单】

2023每日刷题&#xff08;五十三&#xff09; Leetcode—219.存在重复元素II 实现代码 class Solution { public:bool containsNearbyDuplicate(vector<int>& nums, int k) {unordered_map<int, int> m;int n nums.size();for(int i 0; i < n; i) {if(m…

Hexo部署到云服务器后CSS样式无效的问题

Hexo部署到云服务器后CSS样式无效的问题 01 前言 趁活动入手了一个云服务器&#xff08;Linux&#xff09;&#xff0c;打算简单挂个博客上去&#xff0c;因为之前部署到github有了一些经验&#xff0c;所以还是选择使用Hexo。中间步骤略&#xff0c;部署完使用浏览器访问的时…

计算机基础知识66

Auth的补充 #概念&#xff1a;是django 的一个app&#xff0c;关于用户的登录&#xff0c;退出&#xff0c;注册... # 配置文件中配置&#xff1a;表会被迁移 INSTALLED_APPS [django.contrib.auth,] # auth有哪些表---权限控制&#xff1a; Permission&#xff1a;auth_permi…

m.2固态硬盘怎么选择?

一、什么是固态硬盘 固态硬盘又称SSD&#xff0c;是Solid State Drive的简称&#xff0c;由于采用了闪存技术&#xff0c;其处理速度远远超过传统的机械硬盘&#xff0c;这主要是因为固态硬盘的数据以电子的方式存储在闪存芯片中&#xff0c;不需要像机械硬盘那样通过磁头读写磁…

智能制造和低代码:打造高效工厂的关键

引言 随着全球制造业进入数字化时代&#xff0c;智能制造和低代码技术已经成为实现高效工厂运营的关键。这两个关键因素的融合为制造业带来了巨大的机会&#xff0c;使企业能够更灵活地应对市场需求、提高生产效率和降低成本。本文将深入探讨智能制造和低代码技术如何共同塑造…

Java到底是什么?学了我们能做什么?

一、Java是什么&#xff1f; Java是一门面向对象编程语言&#xff0c;不仅吸收了C语言的各种优点&#xff0c;还摒弃了C里难以理解的多继承、指针等概念&#xff0c;因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表&#xff0c;极好地实…

力扣257. 二叉树的所有路径(递归回溯与迭代)

题目&#xff1a; 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,null,5] 输出&#xff1a;["1->2->5","…

Java简易版:UDP协议实现群聊

要先 运行服务端&#xff0c;在运行客户端&#xff0c;否则会报错。 服务端&#xff1a; package 二十一章;import java.io.*; import java.net.*; import java.util.ArrayList; public class T{public static ServerSocket server_socket;public static ArrayList<Socket…