一篇文章帮你弄懂邻接矩阵,邻接表和链式前向星的区别

news2024/11/16 9:58:13

前言:

在学C++的时候,面对各种各样的存图方式,脑子都大了不少,各种算法还在向我冲来,结果一个邻接矩阵/邻接表/链表轻松给了我一下暴击就直接让我KO了,趁着脑子还算清楚,详细的介绍下这三种存图方式的本质与代码实现。

思路实现:

链式前向星、邻接矩阵适合存稠密图,而邻接表则适合存稀疏图,做题时可根据不同的数据范围来酌情选择三种方式来存储图。
既然提到了图,那么下面先来介绍一下。

一、图是什么?

图是一种数据结构,对于初学者来说,可以理解图就是有许多点用边连起来就叫做图。
图又分无向图和有向图,无向图即指两个点之间没有明确的指向性,两个点可以互相访问,遍历。
而有向图则于此不同,在输入完指向性后计算机只能按照先前建好的有向图进行遍历。


在这里插入图片描述
两个图较为相似,但是有些细节还是非常不同的。但是有向图和无向图的输入是一模一样,意思却略有不同,这里拿上图举例:
这里我们假设图中从u-v有一条边,则两图的输入均为:

12
14
51
13

这里拿第一行的"1 2"举个例子;
在无向图中,我可以从①号节点去访问②号节点,也可以从②号节点去访问①号节点;
在有向图中:因为输入循序是先‘1’后‘2’,所以在遍历和访问图的过程中只能从①号节点去访问②号节点。
除去边的关系,图还有一重要东西就是边权,简单来说就是就是访问两个节点所需要花的"代价"。
在这里插入图片描述
这里拿有向图做例子,在上图中不难发现,从①号节点走到②号节点的边权是3,从②号节点走到③号节点的边权是4…这里边权的作用就是在某些题中需求从某个节点走到某个节点所需的边权总和是多少。
图还分为稠密图和稀疏图,稀疏图就是其边的数量完全小于完全图,稠密图的边数则接近完全图。
讲完了图,下文就给大家逐个介绍一下前向星、邻接矩阵和邻接表存图的原理以及代码实现。

二、链式前向星

链式前向星的本质就是一个链表,但是其还多了一个head数组来记录头结点,利用链表可以根据一个节点去访问另一个节点的特性来进行存图操作。假设其有n个点,m条边。则其空间效率和空间效率均为O(m),适合存储稠密图。下面用代码来解释原理;

struct EDGE{
	int v,w,next;
	//v变量记录的是当前点到达那一条边。
	//w变量记录的是走过当前这条边所需的"代价"即权值
	//next变量记录的是以某一个定点为起点,读的上一条边的编号。
	//这三个变量的意义一定要搞明白,不搞明白这个,前向星的写法死记硬背也不一定行。
}e[N<<1];
int head[N],tot=1;
void add(int u,int v,int w)
{
	e[tot].v=v;
	e[tot].w=w;
	e[tot].next=head[u];
	head[u]=tot++;
}

假设这里有一个图:
在这里插入图片描述
这是一个有向带权图,所以其各边之间的输入应为:
在这里插入图片描述
对应成结构体数组里的v,w,next三种变量就应该是:
在这里插入图片描述
不难看出,这里tot记录的,就是上一行的编号,也就是说,我可以通过tot找到上一行的编号,从而找出先前一次存图的to,w,next;
那么head数组又是干啥的呢?让我们再画个图;
在这里插入图片描述

通过此图可知,head数组实则就是记录的以第i个编号的点为起点,我读进去的最后一条边的编号。
对于不明白者,博主在举一个例子根据代码和图来进行讲解。
可以说,head数组就是链式前向星的核心思路

假设我们要找起点为1,终点为4,边权是9的这条边。
因为起点是1,所以我们找到head[1]里面存的值是4,说明我们以1为起点读进去的最后一条边的编号为4。
在这里插入图片描述
根据编号,我们找到上图横线的那组数据,至此,head数组的作用就发挥完了,接下来该next变量上场了。
由上图红线那一行的next变量值为3,说明上一条以1为起点的边编号是3,所以我们找到编号为3的那一行,由于不是我们要找的东西,所以我们再看编号为3的那一行的next是2,所以我们访问编号为2的那一行,结果发现,正是我们要找的那一条边,随即结束查找。
在这里插入图片描述
所以,我们由编号为3的那一条边找到编号为2的那一条边在找到结果那条边,正如上图所以,不断的搜寻不正是如一条链式的嘛,遂此存图方式叫做链式前向星。
原理看明白,接下来我会把代码的每一步根据上文所讲的图例及原理一一拆分讲解。

代码讲解:

我们将其分成两部分,一部分是结构体数组,另一部分是建边add操作。

struct EDGE{
	int v,w,next;
}e[N<<1];

结构体数组里存储的’v,w,next’分别是指向的那条边,边权和以某一个定点为起点,读的上一条边的编号。

void add(int u,int v,int w)
{
	e[tot++].v=v;//将指向的边存进去。这里注意一个点,此时是新的一条边,cnt要++;
	e[tot].w=w;//将当前的边的边权存进去
	//下面两步是最为核心的两步。
	e[tot].next=head[u];
	//我们说了,next数组存的是以某个定点为起点,读的上一条边的编号。head数组里记录的是以某个点为起点,
	//我读进去的最后一条边的编号。想一下,此时的e[tot]的这条边还没有加进去,所以此时的e[tot]的数组的
	//上一条边的编号就是我head数组存的最后一条边的编号。所以我得e[tot].next=head[u]就行啦!
	head[u]=tot;
	//此时我已经读进去了新的一条边,那此时head最后指向的是不是就是当前的边?就是此时的cnt呀,我直接赋
	//值给head[u]就行
}

呼~这就是链式前向星的的思路+代码,博主自认为讲的算是比较详细了,在写的同时也捋了一自己的思路,遂采用了最笨拙的方法。接下来给大家讲解邻接矩阵!

三、邻接矩阵

矩阵矩阵,一听这名字大家就大概能猜到这是一个二维数组(这里我要提一下,谁以为是一维数组就直接不用学了,你脑子太笨。。。就比如我。。。
扯远了,拉回~但是这不是简简单单二维数组,在无边权的时候,数组里存储的数字’1’、‘0’,关于为啥要存着两个数呢,待会讲解,那对于有边权的图,我们数组里存储的就是边权啦。
不同于链式前向星,邻接矩阵要考虑有向图,无向图,带权图…但是其思路又要比前向星略微简单一些,两者都是存储稠密图。
我们先从有向图和无向图的角度考虑 (1) 有向图:
对于有向图来说,我从第i个点走到第j个点和从第j个点走到第i个点完全是两条不同的道路,如果不提前加进图中,不提前声明的情况下是无法走的,也可以理解成e[i][j]!=e[j][i];
(2) 无向图:
无向图则和有向图截然不同,如果我说从i到j有一条边可以走,那么我从i走到j和从j走到i都是可行的,可以理解成e[i][j]==e[j][i];
说完了有向&无向图,就到了带权与不带权的问题了,再说这个之前,我们先说一下究竟是怎么用二维数组来存储边权或者是之前提到的’0’ '1’呢?
众所周知,二维数组的写法是a[i][j]的,那我们假设从第2条边到第3条边有一个边权为4的边,那我们就可以在
e[2][3]中存一个4的值,如果我们想查看他是从哪条边指向哪条边的,是不是直接用双重for循环看一下他的i和j值就可以啦。就类似下图。

#include<iostream>
using namespace std;
int main()
{
	int m;
	int u,v,w;
	int e[10][10];
	for(int i=0;i<m;i++)
	{
		cin>>u>>v>>w;
		e[u][v]=w;
	}
	return 0;
}

在上面的代码里我们可以看见,我们的两个点是找到其边权的基础,如果题目中想查询2-3的边权,直接输出
e[2][3]就可以。当然,这只是有向带权图的代码,无向带权图只需在e[u][v]=w的后面再反向建一次边就可以了。
说完了带权图,那么不带权图该怎么写呢?
我们的思路是如果从i-j这个点有一条边,那么我们e[i][j]的值就赋为1,输入完一遍以后,我们再遍历一遍这个e数组,里面凡是不为1这个值的点就说明从没有从i-j的这一条边,所以将这些中间没边的值都赋为0。
因为这里没有代码,所以i和j说了多次,不明白的同学可以看下面的代码解释.

#include<iostream>
using namespace std;
const int N=10005;
int e[N][N];
int main()
{
	int m;//m条边
	for(int i=0;i<m;i++)
	{
		int u,v;//说明从u-v有一条边
		e[u][v]=1;//其值赋为1
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(e[i][j]!=1) e[i][j]=0;//!=1说明之前没有输入过,说明i-j没有边,赋为0;
		}
	}
	return 0;
}

无带权无向图的处理方法和之前带权无向图的处理方式大致无异,这里就不多赘述了。

三、邻接表

邻接表是擅长存储稀疏图,其主要是来替代邻接矩阵来存储一些用邻接矩阵会十分耗费空间复杂度的图,但是博主接触不多,这里就省掉啦。。。以自己的做题经验除非非常恶心是不会在空间上卡你的,只要够用用邻接矩阵即可。
如果后续理解透彻,再更新一期。

创作不易请勿白嫖啊www!麻烦给作者个赞+三联!谢啦~

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

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

相关文章

探索工程机械远程控制新纪元:Intewell-Hyper II震撼发布!

在当前的工程技术领域&#xff0c;远程控制技术以其卓越的效率和方便性&#xff0c;正受到越来越多的关注和运用。而在这个过程中&#xff0c;某机械集团以Intewell-HyperII操作系统为基础&#xff0c;打造出了具有前瞻性的工程机械远程控制器&#xff0c;为行业的发展提供了新…

树莓派-搭建wireguard服务

一、简介 官网&#xff1a;https://www.wireguard.com/ WireGuard is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headac…

SpringBoot原理分析 | 安全框架:Shiro

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Shiro Shiro是一个安全框架&#xff0c;用于认证、授权和管理应用程序的安全性。它提供了一组易于使用的API和工具&#xff0c;可以帮助您轻松地添加安全性到您的应用…

apple pencil到底值不值得买?好用的iPad电容笔

随着ipad平板型号版本的不断更新&#xff0c;其的功能越来越多&#xff0c;现在它的性能已经可以和笔记本电脑相媲美了。而现在&#xff0c;随着技术的进步&#xff0c;IPAD已经不再是单纯的娱乐&#xff0c;而是一种功能强大的学习、绘画、工作等等。要增加生产效率&#xff0…

按键消抖(有/无状态机)

一&#xff0c;理论概念 按键抖动 按键抖动&#xff1a;按键抖动通常的按键所用开关为机械弹性开关&#xff0c;当机械触点断开、闭合时&#xff0c;由于机械触点的弹性作用&#xff0c;一个按键开关在闭合时不会马上稳定地接通&#xff0c;在断开时也不会一下子断开。因而在闭…

<MyBatis>MyBatis把空字符串转换成了0的问题处理方案

先看问题: Postman入参: MyBatis采用map循环插入: // Mapper接口层void addPar(Param(value "question") Map<String, Object> paramMap);<!-- 新增&#xff1a;参数 --><insert id"addPar" parameterType"map">INSERT IGNO…

高忆管理:集合竞价可以卖股票吗?

集合竞价是证券买卖所开盘前的一种买卖方式&#xff0c;其意图是为了在买卖所开盘之前构成有用的商场价格。因为商场处于初始状况&#xff0c;买卖量较小&#xff0c;因而集合竞价价格或许与买卖日中的实践买卖价格有所不同。那么&#xff0c;集合竞价是否能够卖股票呢&#xf…

Python3,1行代码,批量把图片转换成PDF文档,女神终于同意跟我吃夜宵了。

批量图片转换成PDF文档 1、引言2、代码示例2.1 安装2.2 单张转换2.3 批量转换 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 求助&#xff0c;求助。 小鱼&#xff1a;有啥事&#xff0c;这大惊小怪的。 小屌丝&#xff1a;我女神跟我说&#xff0c; 如果我把她的照片…

Spark 5:Spark Core 内核调度

DAG Spark的核心是根据RDD来实现的&#xff0c;Spark Scheduler则为Spark核心实现的重要一环&#xff0c;其作用就是任务调度。Spark的任务调度就是如何组织任务去处理RDD中每个分区的数据&#xff0c;根据RDD的依赖关系构建DAG&#xff0c;基于DAG划分Stage&#xff0c;将每个…

跨境电商ERP源码选型指南,如何找到最适合你的?

在跨境电商行业&#xff0c;一个高效的ERP系统是保证业务顺利进行和管理的关键。选择适合自己的跨境电商ERP源码至关重要。本指南将帮助你了解如何找到最适合你的跨境电商ERP源码。 跨境电商ERP源码的重要性 跨境电商ERP源码在现代电商营运中起着至关重要的作用。它提供了一套…

音频开发-小程序和H5

微信录音 1、引入sdk 2、录音操作 浏览器录音 参考文献&#xff1a;前端H5实现调用麦克风&#xff0c;录音功能_h5 录音_Darker丨峰神的博客-CSDN博客 function record() { window.navigator.mediaDevices.getUserMedia({ audio: { sampleRate: 44100, // 采样率 channelCount…

游戏APP开发:创新设计的秘诀

在游戏 APP开发中&#xff0c;创新设计是游戏开发公司的一大追求&#xff0c;为了可以为用户带来更好的游戏体验&#xff0c;这就需要对游戏 APP开发进行创新设计。那么&#xff0c;游戏 APP开发中的创新设计是什么呢&#xff1f;接下来&#xff0c;我们就一起来看看吧。 想要…

一起学算法(递推篇)

前言&#xff1a;递推最通俗的理解就是数列&#xff0c;递推和数列的关系就好比算法和数据结构的关系&#xff0c;数列有点像数据结构中的顺序表&#xff0c;而递推就是一个循环或者迭代的过程的枚举过程 1.斐波那契数列 斐波那契数形成的序列称为斐波那契数列&#xff0c;该…

【Java|golang】143. 重排链表---快慢指针

给定一个单链表 L 的头节点 head &#xff0c;单链表 L 表示为&#xff1a; L0 → L1 → … → Ln - 1 → Ln 请将其重新排列后变为&#xff1a; L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是单纯的改变节点内部的值&#xff0c;而是需要实际的进行节点交换。 …

python中有哪些异常,怎么处理

目录 python报的错误怎么处理 1. 使用 try-except 语句块 2. 使用 finally 语句块 3. 主动引发异常 python中有哪些异常 不知道是什么异常时怎么操作 总结 python报的错误怎么处理 在Python中&#xff0c;当程序执行时遇到错误&#xff0c;Python会抛出异常。要处理Pyt…

孩子近视有必要用全光谱灯吗?全光谱led灯推荐

当然&#xff0c;有必要!全光谱LED灯的光源分布更加均匀&#xff0c;使空间更加美观舒适&#xff0c;而普通灯的光源分布可能会在一定范围内分布不均匀。全光谱它的使用寿命长达20-30万小时&#xff0c;而普通灯的使用寿命仅为1000-2000小时&#xff0c;因此在长期使用上&#…

list模拟

之前模拟了string,vector&#xff0c;再到现在的list&#xff0c;list的迭代器封装最让我影响深刻。本次模拟的list是双向带头节点的循环链表&#xff0c;该结构虽然看起来比较复杂&#xff0c;但是却非常有利于我们做删除节点的操作&#xff0c;结构图如下。 由于其节点结构特…

收发存和进销存有什么区别?

一、什么是收发存和进销存 1、收发存 收发存是供应链管理中的关键概念&#xff0c;用于描述企业在供应链中的物流和库存管理过程。 收发存代表了企业在采购、生产和销售过程中的物流活动和库存水平。 收&#xff08;Receiving&#xff09; 企业接收供应商送达的物料或产品…

归并排序算法

归并排序 算法说明与实现代码&#xff1a; 归并排序&#xff08;Merge Sort&#xff09;: 归并排序是一种分治算法&#xff0c;它将列表分成两个子列表&#xff0c;分别进行排序&#xff0c;然后将排序好的子列表合并成一个有序列表。 package mainimport "fmt"fu…

手机商城免费搭建之java商城 开源java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框…