邻接表的网络存储空间很大吗
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了邻接表的网络存储空间很大吗相关的知识,希望对你有一定的参考价值。
在具体讲解邻接表存储图的实现方法之前,先普及一个"邻接点"的概念。在图中,如果两个点相互连通,即通过其中一个顶点,可直接找到另一个顶点,则称它们互为邻接点。邻接指的是图中顶点之间有边或者弧的存在。
邻接表存储图的实现方式是,给图中的各个顶点独自建立一个链表,用节点存储该顶点,用链表中其他节点存储各自的临界点。
与此同时,为了便于管理这些链表,通常会将所有链表的头节点存储到数组中(也可以用链表存储)。也正因为各个链表的头节点存储的是各个顶点,因此各链表在存储临界点数据时,仅需存储该邻接顶点位于数组中的位置下标即可。
例如,存储图 1a) 所示的有向图,其对应的邻接表如图 1b) 所示:
邻接表存储有向图
图 1 邻接表存储有向图
拿顶点 V1 来说,与其相关的邻接点分别为 V2 和 V3,因此存储 V1 的链表中存储的是 V2 和 V3 在数组中的位置下标 1 和 2。
从图 1 中可以看出,存储各顶点的节点结构分为两部分,数据域和指针域。数据域用于存储顶点数据信息,指针域用于链接下一个节点,如图 2 所示:
邻接表节点结构
图 2 邻接表节点结构
在实际应用中,除了图 2 这种节点结构外,对于用链接表存储网(边或弧存在权)结构,还需要节点存储权的值,因此需使用图 3 中的节点结构:
邻接表存储网结构使用的节点
图 3 邻接表存储网结构使用的节点
图 1 中的链接表结构转化为对应 C 语言代码如下:
#define MAX_VERTEX_NUM 20//最大顶点个数
#define VertexType int//顶点数据的类型
#define InfoType int//图中弧或者边包含的信息的类型
typedef struct ArcNode
int adjvex;//邻接点在数组中的位置下标
struct ArcNode * nextarc;//指向下一个邻接点的指针
InfoType * info;//信息域
ArcNode;
typedef struct VNode
VertexType data;//顶点的数据域
ArcNode * firstarc;//指向邻接点的指针
VNode,AdjList[MAX_VERTEX_NUM];//存储各链表头结点的数组
typedef struct
AdjList vertices;//图中顶点的数组
int vexnum,arcnum;//记录图中顶点数和边或弧数
int kind;//记录图的种类
ALGraph;
邻接表计算顶点的出度和入度
使用邻接表计算无向图中顶点的入度和出度会非常简单,只需从数组中找到该顶点然后统计此链表中节点的数量即可。
而使用邻接表存储有向图时,通常各个顶点的链表中存储的都是以该顶点为弧尾的邻接点,因此通过统计各顶点链表中的节点数量,只能计算出该顶点的出度,而无法计算该顶点的入度。
对于利用邻接表求某顶点的入度,有两种方式:
遍历整个邻接表中的节点,统计数据域与该顶点所在数组位置下标相同的节点数量,即为该顶点的入度;
建立一个逆邻接表,该表中的各顶点链表专门用于存储以此顶点为弧头的所有顶点在数组中的位置下标。比如说,建立一张图 1a) 对应的逆邻接表:
逆邻接表示意图
对于具有 n 个顶点和 e 条边的无向图,邻接表中需要存储 n 个头结点和 2e 个表结点。在图中边或者弧稀疏的时候,使用邻接表要比前一节介绍的邻接矩阵更加节省空间。
回答于 2022-11-01
抢首赞
老人用哪种造口袋方便-淘宝热卖好物汇集,品牌众多,放心购!

【买3送5】造口袋封条造口护理用品防漏夹子造瘘袋便袋封口条10根
¥42.75 元

怡康一件式开口造口袋造瘘袋人工肛门袋大便袋10个送尾夹2
¥50 元

造口裤挂尿袋裤子老年人裤膀胱造瘘胆手术后护理专用裤春夏薄款
¥116 元

造瘘裤病人护理裤肾造口肠道造口老人护理用品病人手术后装尿袋裤
¥150 元

造瘘裤尿袋裤造口裤膀胱手术装尿袋引流袋裤子病人护理裤老人专用
¥96 元
淘宝热卖广告
哪个牌子好护肤品-上淘宝选好物,轻松下单,放心购物!
哪个牌子好护肤品-淘宝热卖好物,大牌汇聚,畅享购物!热卖优质商品,淘你满意!
淘宝热卖广告
护肤品有哪些品牌-淘宝热卖好物汇集,品牌众多,放心购!
护肤品有哪些品牌-购物上淘宝,品类集结,热卖好物!海量优质商品,轻松畅购!尽享优惠,买东西上淘宝,一站轻松购!
广告
数据结构,求无向图用邻接矩阵和邻接表的存储空间大小,怎么算?
邻接表所需的存储空间为e(边数),但不适合查询两点间是否存在路径邻接矩阵所需的存储空间为你n^2,适合查询两点间是否存在路径对于第二问,邻接表所需的存储空间为9900,邻接矩阵所需的存储空间为你n^2=10000,差不多,所以选性能更优的邻接矩阵实际上像(2)这种稠密图(其实是个满图)一般适合邻接矩阵
司马刀剑
1点赞1评论
更多专家
邻接表的网络存储空间很大吗
专家1对1在线解答问题
5分钟内响应 | 万名专业答主
马上提问
最美的花火 咨询一个电子数码问题,并发表了好评
lanqiuwangzi 咨询一个电子数码问题,并发表了好评
garlic 咨询一个电子数码问题,并发表了好评
188****8493 咨询一个电子数码问题,并发表了好评
篮球大图 咨询一个电子数码问题,并发表了好评
动物乐园 咨询一个电子数码问题,并发表了好评
AKA 咨询一个电子数码问题,并发表了好评
图解:什么是“图”?
作为图的开始,我们先来看一个经典的问题,它被认为是图论的起源。 欧拉在1735年提出,并没有方法能圆满解决这个问题,他更在第二年发表在论文《柯尼斯堡的七桥》中,证明符合条件的走法并不存在 欧拉把实际的抽象问题简化为平面上的点与线组合,每一座桥视为一条线,桥所连接的地区视为点。这样若从某点出发后最后再回到这点,则这一点的线数必须是偶数,这样的点称为偶顶点。相对的,连有奇数条线的点称为奇顶点。由于柯尼斯堡七桥问题中存在4个奇顶点,它无法实现符合题意的遍历。 之后,不少数学家都尝试去解析这类事例。而这些解析,最后发展成为了数学中的图论233。 图是一种非线性表数据结构,图中的元素我们叫做顶点,图中建立的连接关系我们叫做边。,图主要分为四种:无向图、有向图、加权图、加权有向图。 我们把有边有方向的图叫做“有向图”,把边没有方向的图叫做“无向图”,把边带有权重的图叫做“加权图”,这些概念其实都比较容易理解,你可以参考下面的几幅图对比一下。我们可以分别类比生活中的:知乎关注(有向)、微信交友(无向)和QQ好友亲密度(带权值)。 在图的表示中,我们定义 度 的概念。对于无向图而言,一个顶点的 度 是指跟该顶点相连接的边的条数;对于有向图而言,我们分别定义 入度 和 出度 ,顶点的入度表示有多少条边指向这个节点,顶点的出度表示有多少条边以这个节点为起点指向其他节点。 图的存储方法主要有两种:邻接表(Adjacency List)和邻接矩阵(Adjacency Matrix)。我们首先来介绍一下这两种存储方法。 邻接矩阵,顾名思义,就是利用矩阵去描述图,它的底层依赖于一个二维数组。对于无向图而言,如果 顶点i 与 顶点j 之间有边,那么我们就把 A[i][j] 和 A[j][i] 标记为1,它们之间没有边就标记为0;对于有向图而言,如果 顶点 i 到 顶点 j 之间,有一条箭头从 顶点 i 指向 顶点 j 的边,那我们就将 A[i][j] 标记为 1。同理,如果有一条箭头从 顶点j 指向 顶点 i 的边,我们就将 A[j][i] 标记为 1。对于带权图,数组中就存储相应的权重。 我们使用邻接矩阵来表示图,虽然的确很直观明了,但是却比较浪费空间。 其一,对于无向图来说, A[i][j] 永远等于 A[j][i] ,我们只需要使用一半矩阵就可以成功地表示,那另一半空间就被浪费掉了; 其二、如果我们存储的是稀疏图,也就是顶点很多,但每个顶点的边并不很多,此时邻接矩阵的存储方法就更加浪费空间了。好比微信有好几亿的用户,对应到图上就是好几亿的顶点。但是每个用户的好友并不会很多,一般也就几百个而已。如果我们用邻接矩阵来存储,那绝大部分的存储空间都被浪费了。 总结一下,当图为稀疏图、顶点较多,即图结构比较大时,更适宜选择邻接表作为存储结构。当图为稠密图、顶点较少时,使用邻接矩阵作为存储结构较为合适。 我们使用一个以顶点为索引的列表数组,其中数组中的每个元素都指向一个单独的链表,该链表存储了与数组中顶点相邻的所有顶点。有点绕口,不过我为你准备了一张图,我相信结合图片你肯定可以更好地理解。 相比于邻接矩阵, 邻接表比较节省存储空间,但是使用起来却比较耗费时间 。不过, 它的形式更为自由和灵活 ,比如,在链表过长的情况下,我们可以把链表用平衡二叉查找树(红黑树)替代,这样的话就比较高效了。 好了,关于图的内容就到这里了,我希望通过这篇文章你对于图有了一个初步的认识!下一次,我们会介绍深度优先搜索和广度优先搜索,小超与你不见不散! 参考技术A 邻接表的网络存储空间不大,它的存储空间只有三个GB,如果你想增加的话需要购买。 参考技术B 邻接表的网络存储空间不大,它的存储空间只有三个GB,如果你想增加的话需要购买。 参考技术C 不大
邻接表的网络存储空间不大,它的存储空间只有三个GB,如果你想增加的话需要购买。 参考技术D 邻接表的网络存储空间不大,它的存储空间只有三个GB,如果你想增加的话需要购买。
多杈树的邻接矩阵实现--插入删除前序遍历
分支任意的树构造的方法主要有两种,邻接矩阵和兄弟链表。邻接矩阵主要用于输入的范围很小的情况,因为矩阵反应的是对应情况,比如树的每一个节点都给与对应顺序的编号的话,如果有100个输入,就需要一个100*100的数组来存储联接关系。
链表的形式一般用于输入量很大的情况,可以很方便的适应。(需要仔细体会一下区别)
邻接矩阵的使用方法:
邻接矩阵的行数代表父节点ID,每一行用来存储其各子节点ID。比如 0 节点下面添加了一个子节点1的话,就需要在数组的第0行的第Tree(邻接数组名)[0]的位置上写入1.这里涉及到下面的小技巧中的每一行的第0位的使用。
邻接矩阵的使用小技巧:
邻接矩阵的每一行的第0位放当前父亲节点的子节点的数量,这样在遍历的时候可以很方便的控制循环。而且在插入新的节点的时候第0位里面的数字就是当前要放入数据的数组的下标(这里的方法很像之前学过的计数排序,把计数值从前一位累加到后一位,这样对应位置里面放的数值就是当前数据应该放在输出数组中的位置。计数排序一会可以再写一个随笔记录一下)。
用法举例:
Tree[fatherID][0]++;//这里是放的当前父亲节点的子节点的数量
Tree[fatherID][Tree[fatherID][0]] = childID;//把当前要插入的子节点放在它应该对应的数组位置上
如果单单是插入的话只需要一个邻接矩阵存储父节点对应的子节点的关系就可以了。但是如果要删除的话,我们往往只是给定当前要删除的节点的标号。这样的话我们没办法找到他的父节点(除非从root节点开始遍历我们刚才生成的邻接数组),这时候我们又需要一个数组来记录子对于父的连接关系(这里就有点像双向链表了,双向链表要实现的也是类似的功能,如果要删除当前节点,只要通过node->pre找到他的父亲节点,然后通过指针操作,把父亲节点的->next指向下一个node,把下一个node的->pre指向前面一个node即可)。于是我们创建一个Father[]数组来存储每一个节点的父亲节点标号。因为所有的节点肯定只有一个父亲(可以有多个孩子,但是只可能有一个父亲,这里仔细体会一下)。
int FatherID[MAX_ID];//存放每一个节点的父节点
我们在插入的时候要把fatherID放到FatherID数组的对应位置上,这样只要知道子节点和FatherID[]数组就可以知道当前的子节点的父亲节点是谁。
对应代码:
void insertNode(int fatherID,int childID){ Tree[fatherID][0]++; Tree[fatherID][Tree[fatherID][0]] = childID; FatherID[childID] = fatherID; }
所以在删除的时候我们要做的就是通过子节点和FatherID[childID] 找到他的父亲节点,然后维护自己创建的数据结构。这里面只有两个数据结构,1.FatherID[childID] 2.int Tree[MAX_ID][MAX_ID]; 因为我们只是通过FatherID数组来找父亲节点,所以嗯。。(纠结了一下)也可以把FatherID对应子节点位置的数组置零,主要是要更改Tree数组的值,
1.要更改Tree[fatherID][0]的值,子节点少了一个。需要把子节点的数目减1
2.要更改Tree[fatherID][Tree[fatherID][0]]的值,子节点已经删去了。需要把对应位置的对应关系删去,即把Tree[fatherID][Tree[fatherID][0]]清零
for(int i=1;i<MAX_ID;i++){ if(Tree[fatherId][i]==Id){ Tree[fatherId][i]=0; Tree[fatherId][0]--;
return; } }
好了进行到最后一个步骤,需要前序遍历生成的树。
刚开始我总是想着要用FatherID这个数组,因为这个数组初始化的时候把对应位置初始化成了自己,这样在向上遍历的时候只要遍历到了自己的话就证明他是根节点。可以作为递归的出口(这里也不是想的很明白。。。汗-_-||等有时间再比较一下数组构建的二叉树的前序,然后更新一下)
但是用当前节点子节点的个数作为递归变量会更好一点(有点像那道给出前序和中序求后序遍历的题目一样,每一次递归的传入量就是当前的前序和中序,用长度来控制传入的前序和中序的字符串,根据每一次的前序提供的根再做下一次的划分,直到当前中序的长度为1,证明不能向下分割了,就是递归的出口)
所以我先把根打印出来,然后对于替换当前的父节点位置,和父节点下面的子节点的个数,如果子节点的个数为零的时候就return。
void preorder(int fatherID,int n){//父亲节点有几个根节点 if(n==0){return;} for(int i=1;i<n+1;i++){ printf("%d ",Tree[fatherID][i]);//root preorder(Tree[fatherID][i],count(Tree,Tree[fatherID][i]));//从前到后面遍历每一个节点 } }
哦,count函数就是为了计算当前的节点下面有几个子节点:利用了Tree[][]数组。(这里面特别骚气的循环是和金老师学的。这样可以少定义一个ret遍历,而且可以少一步传值的过程)
int count(int data[MAX_ID][MAX_ID],int fatherID){ int i=1; for(;data[fatherID][i]!=0;i++){ ; } return i-1; }
最终的代码:
#include <stdio.h> #define MAX_ID 20 int Tree[MAX_ID][MAX_ID];//存放对应关系 int FatherID[MAX_ID];//存放每一个节点的父节点 void insertNode(int fatherID,int childID){ Tree[fatherID][0]++; Tree[fatherID][Tree[fatherID][0]] = childID; FatherID[childID] = fatherID; } void delNode(int Id){ int fatherId = FatherID[Id]; FatherID[Id] = 0; for(int i=1;i<MAX_ID;i++){ if(Tree[fatherId][i]==Id){ Tree[fatherId][i]=0; Tree[fatherId][0]--; return; } } } //计算当前父亲节点有几个子节点 int count(int data[MAX_ID][MAX_ID],int fatherID){ int i=1; for(;data[fatherID][i]!=0;i++){ ; } return i-1; } void preorder(int fatherID,int n){//父亲节点有几个根节点 if(n==0){return;} for(int i=1;i<n+1;i++){ printf("%d ",Tree[fatherID][i]);//root preorder(Tree[fatherID][i],count(Tree,Tree[fatherID][i]));//从前到后面遍历每一个节点 } } void init(){ for(int i=0;i<MAX_ID;i++){ for(int j=0;j<MAX_ID;j++){ Tree[i][j] = 0; } } for(int i=0;i<MAX_ID;i++){ FatherID[i] = i;//指向它自己 } } int main(){ insertNode(0,1); insertNode(0,2); insertNode(0,3); insertNode(0,4); insertNode(1,5); insertNode(1,7); insertNode(2,8); insertNode(3,9); insertNode(4,10); insertNode(4,11); insertNode(11,12); insertNode(10,13); delNode(12); delNode(13); int num = count(Tree,0); printf("0 "); preorder(0,num); return 0; }
递归的过程还是不是那么的清晰,要再好好想想。
以上是关于邻接表的网络存储空间很大吗的主要内容,如果未能解决你的问题,请参考以下文章