【开卷数据结构 】图的五大存储方式

简介: 【开卷数据结构 】图的五大存储方式

1.邻接矩阵


图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组 V 存储图中顶点信息,一个二维数组(称为邻接矩阵) A 存储图中的边或弧的信息


设 G=(V,E) 是具有n个顶点的图,顶点的顺序为(v0,v1 ,… ,vn-1),则G的邻接矩阵A:


e26e2d868a5593d9c8eb7785360e8669_6dcace20cabf49bbaab04b825882653a.png


下图是一个无向图和它的邻接矩阵:


b579b25ade2255ace9585c8ee44fe61b_04773bd9a0374fdeb4f08b2716722b54.png


通过观察不难发现:


1)无向图的邻接矩阵是一个对称矩阵,且主对角线都为 0 。

2)我们要知道某个顶点的度,其实就是这个顶点 Vi 在邻接矩阵中第 i 行(或第 i 列)的元素之相。比如顶点 V1 的度就是 0+1+0+1+0=2 。

3)求顶点 vi 的所有邻接点就是将矩阵中第 i 行元素扫描一遍, A[i][j] 为 1 就是邻接点。

下图是一个有向图和它的邻接矩阵:


9529d7529ec2a0d32b178e3687e46a01_c7a1ec31abb14a93aa55382bb009f986.png


 通过观察不难发现:


1)有向图的邻接矩阵不是一个对称矩阵,且主对角线都为 0 。

2)有向图讲究入度与出度,顶点 V1 的入度为 2,正好是第 V1 列各数之和。顶点 V1 的出度为 1,即第 V1 行的各数之和。

对于带权图来说,若顶点 Vi 和 Vj 之间有边相连,则邻接矩阵中对应项存放着该边对应的权值


9a49686ef93431c124e7ff7ba35ca0e2_9164445e05f84ce09370523ecb095a67.png


下图是有向网图和它的邻接矩阵:


2f105183587f5a1f504997e5e1017ef7_4e231bce39864cb38df070cad2f80f39.png


💬 代码演示


通过上文,我们可以定义出邻接矩阵的存储结构:


#define MAXNum  100       //顶点的最大值 
typedef  char  VertexType;    //顶点信息为字符类型
typedef  struct
{   
    VertexType   Vex[MAXNum];    //顶点表 
    int   arcs[MAXNum][MAXNum];  //邻接矩阵
    int   vexnum,arcnum;         //顶点数和边数
}MGraph;


2.邻接表


当一个图为稀疏图时,使用邻接矩阵法显然要浪费大量的存储空间,图的邻接表法结合了序存储和链式存储方法,可以大大减少这种不必要的浪费。

邻接表的处理办法:


 图中顶点用一个一维数组存储,当然也可以用单链表来存储。用数组可以较容易的读取顶点信息,更加方便。另外,对于顶点数组中,每个数据元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息。

图中每个顶点 vi 的所有邻接点构成一个线性表。由于邻接点的个数不定,所以用单链表存储,无向图称为顶点 vi 的边表,有向图则称为以 vi 为弧尾的出边表。

下图是一个无向图的邻接表结构:


d5e277cf7c1832f24cb03d93010a9623_00d5e7bfccde4058bda7637982ebf6c1.png


邻接表存储的图具有的特点:


邻接表表示不唯一。取决于单链表的创建算法和边的输入次序。

对于无向图,邻接表的顶点 vi 对应的第i个链表的边结点数目正好是顶点 vi 的度。

对于有向图,邻接表的顶点 vi 对应的第 i 个链表的边结点数目仅是顶点 vi 的出度。入度为所有邻接点域为 i 的边结点的数目。


💬 代码演示

#define MAXVEX 100  //图中顶点数目的最大值
typedef char VertexType;  
typedef int EdgeType; 
//边表结点
typedef struct EdgeNode{
  int adjvex;                 //该弧所指向的顶点的下标或者位置
  EdgeType weight;         //权值,对于非网图可以不需要
  struct EdgeNode *next;     //指向下一个邻接点
}EdgeNode;
//顶点表结点
typedef struct VertexNode{
  Vertex data;             //顶点域,存储顶点信息
  EdgeNode *firstedge         //边表头指针
}VertexNode, AdjList[MAXVEX];
//邻接表
typedef struct{
  AdjList adjList;
  int numVertexes, numEdges;  //图中当前顶点数和边数
}


3. 十字链表


十字链表是有向图的一种链式存储结构。


在十字链表中,对应于有向图的每条弧有一个结点,对应每一个顶点也有一个结点。


这些结点的结构如下图所示:


cee5afaa04b85a2729eb86de8d629f95_4c8c3be5401a4959a749ccf919919dfa.png


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


顶点结点中有三个域:data 域存放顶点相关的数据信息。firstin 和 firstout 两个域分别指向以该顶点为弧头或弧尾的第一个弧结点。


有向图的十字链表表示法如下图所示:


0433c128baa55addfd743118d56e8f59_a20e7bbdb9794df28d333da3ea9d8743.png


十字链表存储的图具有的特点:


在十字链表中很容易找到以 V1 为尾的弧,也容易找到以 V1 为头的弧,因而容易求得顶点的出度和入度。

图的十字链表表示不是唯一的。


💬 代码演示

#define MAXVEX 100             //图中顶点数目的最大值
typedef char VertexType;  
typedef int EdgeType;         
//边表结点
typedef struct EdgeNode{
  int adjvex;                 //该弧所指向的顶点的下标或者位置
  EdgeType weight;         //权值
  struct EdgeNode *next;     //指向下一个邻接点
}EdgeNode;
//顶点表结点
typedef struct VertexNode{
  Vertex data;             //顶点域,存储顶点信息
  EdgeNode *firstedge         //边表头指针
}VertexNode, AdjList[MAXVEX];
//邻接表
typedef struct{
  AdjList adjList;
  int numVertexes, numEdges;  //图中当前顶点数和边数
}


4.邻接多重表


邻接多重表是无向图的另一种链式存储结构。


在邻接表中,容易求得顶点和边的各种信息,但在邻接表中求两个顶点之间是否存在边而对边执行删除等操作时,需要分别在两个顶点的边表中遍历,效率较低。


与十字链表相似,在临界多重表中,每条边用一个结点表示,其结构如下图所示:


a70030a2644fb21a44c1df61728e1089_2ac53b0ebf22435a9b56a8e31b43a220.png


其中,mark 为标志域,可用以标记该条边是否被搜索过;ivex 和 jvex 为该边依附的两个顶点在图中的位置;liink指向下一条依附于顶点 ivex 的边;jlink 指向下一条依附于顶点 jvex 的边,info 为指向和边相关的各种信息的指针域。


每个顶点也用一个结点表示,其结构如下图所示:


2b8ce7b8e31a33de85ddb82fcb3ef44c_f4318013675f4358863d1b8ced646285.png


其中,data 域存储该顶点的相关信息,firstedge 域指示第一条依附于该顶点的边。


Q:邻接多重表和邻接表有什么区别


A:在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,由于每条边依附于两个顶点,因此每个边结点同时链接在两个链表中。对无向图而言,其邻接多重表和邻接表的差别仅在于,同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。


无向图的邻接多重表表示法如下图所示:


9bbbcc69de55c68290ce07f9765d2bcd_bbbd5ba72ae041c89869d685a8052476.png


邻接多重表的各种基本操作的实现和邻接表类似。


5.边集数组


边集数组是由两个一维数组构成。一个是存储顶点的信息;另一个是存储边的信息。边数组每个数据元素由一条边的起点下标 (begin), 终点下标 (end) 和权 (weight) 组成,如下图所示:


92d917430577163a9047ba61904a018a_e75e440fe509465dad8667b3a4604800.png


显然边集数组关注的是边的集合,在边集数组中要查找一个顶点的度需要扫描整个边数组,效率并不高。因此它更适合对边依次进行处理的操作,而不适合对顶点相关的操作。

9f7c26e05344d0f9774cf39c4e4e3be8_edf2c6f017ba4de49bb993a0a8145230.png


相关文章
|
1月前
|
存储 安全 数据库
除了 HashMap,还有哪些数据结构可以实现键值对存储?
【10月更文挑战第11天】 除了`HashMap`,其他常见支持键值对存储的数据结构包括:`TreeMap`(基于红黑树,键有序)、`LinkedHashMap`(保留插入顺序)、`HashTable`(线程安全)、`B-Tree`和`B+Tree`(高效存储大量数据)、`SkipList`(通过跳跃指针提高查找效率)及`UnorderedMap`(类似`HashMap`)。选择合适的数据结构需根据排序、并发、存储和查找性能等需求。
|
2月前
|
存储 Java
java数据结构,线性表链式存储(单链表)的实现
文章讲解了单链表的基本概念和Java实现,包括头指针、尾节点和节点结构。提供了实现代码,包括数据结构、接口定义和具体实现类。通过测试代码演示了单链表的基本操作,如添加、删除、更新和查找元素,并总结了操作的时间复杂度。
java数据结构,线性表链式存储(单链表)的实现
|
2月前
|
存储 人工智能 C语言
数据结构基础详解(C语言): 栈的括号匹配(实战)与栈的表达式求值&&特殊矩阵的压缩存储
本文首先介绍了栈的应用之一——括号匹配,利用栈的特性实现左右括号的匹配检测。接着详细描述了南京理工大学的一道编程题,要求判断输入字符串中的括号是否正确匹配,并给出了完整的代码示例。此外,还探讨了栈在表达式求值中的应用,包括中缀、后缀和前缀表达式的转换与计算方法。最后,文章介绍了矩阵的压缩存储技术,涵盖对称矩阵、三角矩阵及稀疏矩阵的不同压缩存储策略,提高存储效率。
390 8
|
2月前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
2月前
|
存储 机器学习/深度学习 C语言
数据结构基础详解(C语言): 树与二叉树的基本类型与存储结构详解
本文介绍了树和二叉树的基本概念及性质。树是由节点组成的层次结构,其中节点的度为其分支数量,树的度为树中最大节点度数。二叉树是一种特殊的树,其节点最多有两个子节点,具有多种性质,如叶子节点数与度为2的节点数之间的关系。此外,还介绍了二叉树的不同形态,包括满二叉树、完全二叉树、二叉排序树和平衡二叉树,并探讨了二叉树的顺序存储和链式存储结构。
|
2月前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。
|
2月前
|
存储 Java
java数据结构,线性表顺序存储(数组)的实现
文章介绍了Java中线性表顺序存储(数组)的实现。线性表是数据结构的一种,它使用数组来实现。文章详细描述了线性表的基本操作,如增加、查找、删除、修改元素,以及其他操作如遍历、清空、求长度等。同时,提供了完整的Java代码实现,包括MyList接口和MyLinearList实现类。通过main函数的测试代码,展示了如何使用这些方法操作线性表。
|
3月前
|
存储 Java
数据结构中的哈希表(java实现)利用哈希表实现学生信息的存储
这篇文章通过Java代码示例展示了如何实现哈希表,包括定义结点类、链表类、数组存储多条链表,并使用简单的散列函数处理冲突,以及如何利用哈希表存储和查询学生信息。
数据结构中的哈希表(java实现)利用哈希表实现学生信息的存储
|
3月前
|
存储 缓存 算法
深入解析B树:数据结构、存储结构与算法优势
深入解析B树:数据结构、存储结构与算法优势
|
3月前
|
存储 算法
【数据结构与算法】队列(顺序存储)
【数据结构与算法】队列(顺序存储)
31 0