在线性表中,数据元素之间是被串起来的,仅有线性关系,每个数据元素只有一个直接前驱和一个直接后继。
在树形结构中,数据元素之间有着明显的层次关系,并且每一层上的数据元素可能和下一层中多个元素相关,但只能和上一层中一个元素相关。
图是一种较线性表和树更加复杂的数据结构。在图形结构中,结点之间的关系可以是任意的,图中任意两个数据元素之间都可能相关。
图(Graph)
是由顶点的有穷非空集合V ( G )
和顶点之间边的集合E ( G )
组成,通常表示为: G = ( V , E )
;
V = { v 1 , v 2 , . . . , v n }
,则用∣ V ∣
表示图G中顶点的个数,也称图G 的阶;E = { ( u , v ) ∣ u ∈ V , v ∈ V }
,用∣ E ∣
表示图G中边的条数。注意: 线性表可以是空表,树可以是空树,但图不可以是空图。就是说,图中不能一个顶点也没有,图的顶点集V一定非空,但边集E可以为空,此时图中只有顶点而没有边。
1、有向图
G1 = (v1, E1)
V1 = {1, 2, 3}
E1 = {<1, 2>, <2, 1>, <2, 3>}
2、无向图
G2 = (v2, E2)
V2 = {1, 2, 3, 4}
E2 = {<1, 2>, <1, 3>, <1, 4>, <2, 3>, <2, 4>, ❤️, 4>}
3、简单图
一个图G若满足:
则称图G GG为简单图,上图中G1 和G~2`均为简单图。数据结构中仅讨论简单图。
4、多重图
若图G中某两个结点之间的边数多于一条,又允许顶点通过同一条边和自己关联,则G为多重图。多重图的定义和简单图是相对的。
5、完全图(也称为简单完全图)
|E|
的取值范围为0 ~ n(n-1)/2
,有n(n-1)/2
条边的无向图称为完全图,在完全图中任意两个顶点之间都存在边;|E|
的取值范围为0 ~ n(n-1)
,有n(n-1)
条弧的有向图称为有向完全图,在有向完全图中任意两个顶点之间都存在方向相反的两条弧;6、子图
设有两个图G = ( V , E )
和G ′ = ( V ′ , E ′ )
, 若V ′
是V
的子集,且E ′
是E
的子集,则称G ′
是G
的子图。若有满足V ( G ′ ) = V ( G )
的子图G ′
,则称其为G
的生成子图。上图中G 3 为G 1 的子图。
注意:并非V和E的任何子集都能构成G的子图,因为这样的子集可能不是图,即E的子集中的某些边关联的顶点可能不在这个V的子集中。
7、连通、连通图和连通分量
8、强连通图、强连通分量
注意:强连通图、强连通分量只是针对有向图而言的。一般在无向图中讨论连通性,在有向图中考虑强连通性。
9、生成树、生成森林
10、顶点的度、入度和出度
11、边的权和网
在一个图中,每条边都可以标上具有某种含义的数值,该数值称为该边的权值。这种边上带有权值的图称为带权图,也称网。
12、稠密图、稀疏图
边数很少的图称为稀疏图,反之称为稠密图。稀疏和稠密本身是模糊的概念,稀疏图和稠密图常常是相对而言的。一般当图G满足∣E∣ < ∣V∣log∣V∣
时,可以将G视为稀疏图。
13、路径、路径长度和回路
14、简单路径、简单回路
15、距离
16、有向树
由于图的结构比较复杂,任意两个顶点之间都可能存在联系,因此无法以数据元素在内存中的物理位置来表示元素之间的关系,也就是说,图不可能用简单的顺序存储结构来表示。而多重链表的方式,要么会造成很多存储单元的浪费,要么又带来操作的不便。因此,对于图来说,如何对它实现物理存储是个难题,接下来我们介绍五种不同的存储结构:
图的邻接矩阵存储方式是用两个数组来表示图:一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
A[i][j]
为1就是邻接点;A[i][j]
是否为1即可;通过以上对无向图、有向图和网的描述,可定义出邻接矩阵的存储结构:
#define MaxVertexNum 100 //顶点数目的最大值
typedef char VertexType; //顶点的数据类型
typedef int EdgeType; //带权图中边上权值的数据类型
typedef struct{
VertexType Vex[MaxVertexNum]; //顶点表
EdgeType Edge[MaxVertexNum][MaxVertexNum]; //邻接矩阵,边表
int vexnum, arcnum; //图的当前顶点数和弧树
}MGraph;
注意:
① 在简单应用中,可直接使用二维数组作为图的邻接矩阵(顶点信息等均可省略);
② 当邻接矩阵中的元素仅表示相应的边是否存在时,EdgeType可定义为值为0和1的枚举类型;
③ 无向图的邻接矩阵是对称矩阵,对规模特大的邻接矩阵可以采用压缩存储;
④ 邻接矩阵表示法的空间复杂度为0(n2),其中n为图的顶点数|V|;
⑤ 用邻接矩阵存储图,很容易确定图中任意两个顶点之间是否有边相连。但是,要确定图中有多少边,则必须按行、按列对每个元素进行检测,所花费的时间代价很大;
⑥ 稠密图适合使用邻接矩阵的存储表示;
当一个图为稀疏图时(边数相对顶点较少),使用邻接矩阵法显然要浪费大量的存储空间,如下图所示:
而图的邻接表法结合了顺序存储和链式存储方法,大大减少了这种不必要的浪费;
邻接表:是指对图G中的每个顶点Vi建立一个单链表,第i个单链表中的结点表示依附于顶点Vi的边(对于有向图则是以顶点Vi为尾的弧),这个单链表就成为顶点Vi的边表(对于有向图则称为出边表)。
边表的头指针和顶点的数据信息采用顺序存储(称为顶点表),所以在邻接表中存在两种结点:顶点表结点和边表结点,如下图示:
有向图的邻接表的实例,如下图所示:
此时我们很容易就可以算出某个顶点的入度或出度是多少,判断两顶点是否存在弧也很容易实现。
对于带权值的网图,可以在边表结点定义中再增加一个weight的数据域,存储权值信息即可。
图的邻接表存储结构,定义如下:
#define MAXVEX 100 //图中顶点数目的最大值
type 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; //图中当前顶点数和边数
}
图的邻接表存储方法具有以下特点: