大话数据结构学习笔记 - 图
图的定义
图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:
, 其中G
表示一个图, V
是图G
中顶点的集合,E
是图G
中边的集合
- 数据元素在线性表中被称为元素,在树中被称为结点,而在图中被称为顶点(
Vertext
) - 顶点集合
V
有穷非空 - 图中顶点之间的逻辑关系用边来表示,边集可以为空
各种图定义
- 无向边:若顶点
到
之间的边没有方向,则称这条边为无向边(
Edge
), 用无序偶对( )来表示 - 无向图:若图中任意两个顶点之间的边都是无向边,则为无向图(
Undirected graphs
) - 有向边:若从顶点
到
之间的边有方向,则称这条边为有向边,也成为弧(
Arc
),用有序偶 表示, 称为弧尾, 称为弧头 - 有向边:如果图中任意两个顶点之间的边都是有向边,则称该图为有向图(
Directed graphs
) - 无向边用小括号
()
表示,有向边用尖括号<>
- 简单图:不存在顶点到其自身的边,且同一条边不重复出现,这样的图为简单图
- 无向完全图:任意两个顶点都存在边的无向图。 含有
n
个顶点的无向完全图有 条边 - 有向完全图:任意两个顶点之间存在互为相反的两条弧的有向图。含有
n
个顶点的有向完全图有 条边 - 稀疏图:有很少条边或弧的图称为稀疏图,反之为稠密图
- 权(
Weight
):与图的边或弧相关的数叫做权 - 网(
Network
):带权的图统称为网 - 子图(
Subgraph
):假设有两个图 和 , 如果 且 , 则称 为 的子图
图的顶点与边间关系
- 无向图的邻接点(
Adjacent
):对于无向图 , 如果边 , 则称顶点 和 互为邻接点。即 和 相邻接。边 依附于顶点 和 ,或者说 与顶点 和 相关联 无向图的度(
Degree
):顶点v
的度是和v
相关联的边的数目, 记为有向图的邻接: 对于有向图 ,如果弧 , 则称顶点
v
邻接到顶点 , 顶点 邻接自顶点 。弧 和顶点 相关联.- 有向图的入度(
InDegree
):以顶点v
为头的弧的数目称为v
的入度, 记为 - 有向图的出度(
OutDegree
):以顶点v
为尾的弧的数目称为v
的出度 , 记为 - 有向图的度: 顶点
v
的度为 - 回路或环(
Cycle
):第一个顶点到最后一个顶点相同的路径称为回路或环 - 简单路径:序列中顶点不重复出现的路径称为简单路径
- 简单回路或简单环:除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,称为简单回路或简单环
连通图相关术语
- 连通图(
Connected Graph
):在无向图 中,如果从顶点 到顶点 有路径,则称 和 是连通的。如果对于图中任意两个顶点 , 和 都是连通的, 则称 是连通图 - 连通分量:无向图中的极大连通子图称为连通分量
- 强连通图: 在有向图 中,如果对于每一对 , 从 到 和从 到 都存在路径,则称 是强连通图。
- 强连通分量:有向图中的极大强连通子图称作有向图的强连通分量
- 生成树:一个连通图的生成树是一个极小的连通子图,它含有图中全部的
n
个顶点,但只有足以构成一棵树的n - 1
条边 - 有向树:如果一个有向图恰有一个顶点的入度为
0
, 其余顶点的入度均为1
, 则是一棵有向树
图的抽象类型
ADT 图(Graph)
Data
顶点的有穷非空集合和边的集合
Operation
CreateGraph(*G, V, VR):按照顶点集 V 和边弧集 VR 的定义构造图 G
DestroyGraph(*G):图 G 存在则销毁
LocateVex(G, u):若图 G 中存在顶点 u, 则返回图中的位置
GetVex(G, v):返回图 G 中顶点 v 的值
PutVex(G, v, value):将图 G 中顶点 v 赋值 value
FirstAdjVex(G, *v):返回顶点 v 的一个邻接顶点, 若顶点在 G 中无邻接顶点返回空
NextAdjVex(G, v, *w):返回顶点 v 相对于顶点 w 的下一个邻接顶点,若 w 是 v 的最后一个邻接点则返 回空
InsertVex(*G, v):在图 G 中增添新节点 v
DeleteVex(*G, v):删除图 G 中顶点 v 及其相关的弧
InsertArc(*G, v, w):在图 G 中增点弧 <v, w>, 若 G 是无向图,还需要增添对称弧 <w, v>。
DeleteArc(*G, v, w):在图 G 中删除弧 <v, w>,若 G 是无向图,则还需要删除对称弧 <w, v>。
DFSTraverse(G):对图 G 中进行深度优先遍历,在遍历过程对每个顶点调用
HFSTraverse(G):对图 G 中进行广度优先遍历,在遍历过程对每个顶点调用
endADT
图的存储结构
对于图的存储结构,简单的顺序存储结构以及多重链表都无法使用,最常用的是 邻接矩阵 和 邻接表
邻接矩阵
图的邻接矩阵(Adjacency Matrix
)存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息
设图中G
有n
个顶点,则邻接矩阵是一个
的方阵,定义为
无向图邻接矩阵示意图
有向图邻接矩阵示意图
邻接矩阵的缺点是对于边数相对顶点较少的图,比较浪费存储空间
图的邻接矩阵存储结构
typedef char VertexType; /* 顶点类型 */ typedef int EdgeType; /* 边上的权值类型 */ #define MAXVEX 100 /* 最大顶点数 */ #define INFINITY 65535 /* 65535 代表无限大 */ typedef struct { VertexType vexs[MAXVEX]; /* 顶点表 */ EdgeType arc[MAXVEX][MAXVEX]; /* 邻接矩阵, 可看做边表 */ int numVertexes, numEdges; /* 图中当前的顶点数和边数 */ }MGraph;
无向网图的邻接矩阵表示建立
/* 无向网图的邻接矩阵表示 */ void CreateMGraph(MGraph *G) { int i, j, k, w; printf("输入顶点数和边数:\n"); scanf("%d,%d", &G->numVertexes, &G->numEdges); /* 输入顶点数和边数 */ for(i = 0; i < G->numVertexes; i++) /* 输入顶点信息,建立顶点表 */ scanf("%d", &G->vecs[i]); for(i = 0; i < G->numVertexes; i++) for(j = 0; j < G->numVertexes; j++) G->arc[i][j] = INFINITY; /* 邻接矩阵初始化 */ for(k = 0; k < G->numEdges; k++) /* 读入 numEdges 条边,建立邻接矩阵 */ { printf("输入边(vi, vj)上的下标i, 下标j 和权 w:\n"); scanf("%d,%d,%d", &i, &j, &w); /* 输入变(vi, vj)上的权 w */ G->arc[i][j] = w; G->arc[j][i] = G->arc[i][j]; /* 因为是无向图,矩阵对称 */ } }
链接表
邻接表(Adjacencty List
)是图的一种链式存储,并且将数组与链表相结合的表示方法。即图中顶点用一维数组存储,每个顶点的所有邻接点用链表存储
无向图的邻接表结构示意图
- 方便查找某个顶点的度,只需要查找顶点的边表中结点的个数
- 判断两定点是否存在边,只需要测试一顶点的边表中是否存在另一节点的下标即可
- 求某顶点的所有邻接点,只需要遍历边表即可
有向图的邻接表结构示意图
- 方便得到每个顶点的出度,若想得到入度,可建立逆邻接表。
图的邻接表存储结构
typedef char VertexType; /* 顶点类型 */ typedef int EdgeType; /* 边上的权值类型 */ typedef struct EdgeNode { int adjtex; /* 邻接点域,存储该顶点对应的下标 */ EdgeType weight; /* 用于存储权值,对于非网图可以不需要 */ struct EdgeNode *next; /* 链域,指向下一个邻接点 */ }EdgeNode; typedef struct VertexNode { VertexType data; EdgeNode *firstedge; }VertexNode, AdjList[MAXTEX]; typedef struct { AdjList adjList; int numVertexes, numEdges; }GraphAdjList;
建立图的邻接表结构
/* 建立图的邻接表结构 */ void CreateALGraph(GraphAdjList *G) { int i, j, k; EdgeNode *e; printf("输入顶点数和边数:\n"); scanf("%d,%d", &G->numVertexes, &G->numEdges); /* 输入顶点数和边数 */ for(i = 0; i < G->numVertexes; i++) /* 输入顶点信息,建立顶点表 */ { scanf(&G->adjList[i].data); /* 输入顶点信息 */ G->adjList[i].firstedge = NULL; /* 将边表置为空表 */ } for(k = 0; k < G->numEdges; k++) /* 建立边表 */ { printf("输入边(vi,vj)上的顶点序号:\n"); scanf("%d,%d", &i, &j); /* 输入边(vi,vj)上的顶点序号 */ e = (EdgeNode *)malloc(sizeof(EdgeNode)); /* 向内存申请空间,生成边表结点 */ e->adjtex = j; /* 邻接序号为 j */ e.next = G->adjList[i].firstedge; /* 将 e 指针指向当前顶点指向的结点 */ G->adjList[i].firstedge = e; /* 将当前顶点的指针指向 e */ e = (EdgeNode *)malloc(sizeof(EdgeNode)); * 向内存申请空间,生成边表结点 */ e->adjnext = i; /* 邻接序号为 i */ e->next = G->adjList[j].firstedge; /* 将 e 指针指向当前顶点指向的结点 */ G->adjList[j].firstedge = e; /* 将当前顶点的指针指向 e */ } }
结语
后续会继续整理图的相关知识,本文章的代码code