数据结构—图
图的定义与基本术语
-
图:G=(V, E)
V: 顶点(数据元素)的有穷非空集合
E: 边的有穷集合
-
有向图和无向图:前者边有方向,后者边无方向

- 完全图:任意两个点都有一条边相连
- 无向完全图:一张图中每条边都是无方向的;在无向完全图中;在无向完全图中:n个顶点,有n(n-1)/2条边
- 有向完全图:图中各边都有方向,且每两个顶点之间都有两条方向相反的边连接的图;在有向完全图中:n个顶点,有**n(n-1)**条边

-
稀疏图:有很少的边或弧的图 (e < nlogn)
-
稠密图:有较多边或弧
-
网:边/弧带权的图
-
邻接:有边/弧相连的两个顶点之间的关系 ()无向图,无先后关系;<>有向图,有先后关系
- 存在(Vi,Vj),则称Vi和Vj互为邻接点
- 存在<Vi,Vj>,则称Vi邻接到Vj,则称Vj邻接于Vi
-
关联(依附):边/弧与顶点之间的关系。
- 存在(Vi,Vj)/<Vi,Vj>,则称该边/弧关联于Vi和Vj
-
顶点的度:与该顶点相关联的边的数目,记为TD(v)
- 在有向图中,顶点的度等于该顶点的入度与出度之和
- 入度:以当前顶点为终点的有向边的条数,记作ID(v)
- 出度:以当前顶点为始点的有向边的条数,记作OD(v)
- 在有向图中,顶点的度等于该顶点的入度与出度之和

-
当有向图中仅有一个顶点的入度为0,其余顶点的入度均为1(出度任意),则该图为树形,称为有向树
-
路径:接续的边构成的顶点序列

-
路径长度:路径上边或弧的数目/权值之和 (有权值按权值算)
-
回路(环):第一个顶点和最后一个顶点相同的路径
-
简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径
-
简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径

-
连通图(强连通图):在无(有)向图G = (V, {E})中,若对任意两个顶点v, u都存在从v到u的路径,则称G是连通图(强连通图)。无向图称为连通图,有向图称为强连通图


-
权与网
- 权:图中边或弧具有的相关树称为权,表明从一个顶点到另一个顶点的距离或耗费
- 网:带权的图称为网
-
子图
设有两个图G = (V, {E}), G1 = (V1, {E1}), 若V1∈V,E1∈E,则称G1是G的子图 (即顶点和边都是图的子集)

-
连通分量 (强连通分量)
- 连通分量:无向图G的极大连通子图称为G的连通分量
- 极大连通子图:该子图是G的连通子图D,将G中任何一个不在D中的顶点加入,子图不再连通的,则该子图称为极大连通子图
- 强连通分量:有向图G的极大连通子图称为G的强连通分量
- 极大连通子图:该子图是G的连通子图D,将G中任何一个不在D中的顶点加入,子图不再连通的,则该子图称为极大连通子图
- 连通分量:无向图G的极大连通子图称为G的连通分量

单顶点就是强连通分量
- 极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,该子图不再连通
- 生成树:包含无向图G所有顶点的极小连通子图
- 生成森林:对非连通图,由各个连通分量的生成树的集合
小世界理论
你和任何一个陌生人之间所间隔的人不会超过五个,也就是说,最多通过五个人你就能够认识任何一个陌生人。”根据这个理论,你和世界上的任何一个人之间只隔着五个人,不管对方在哪个国家,属哪类人种,是哪种肤色。
将小世界理论的人际关系网络抽象成一个无向图G,用图G中的一个顶点表示一个人,两个人认识与否用代表这两个人的顶点之间是否有一条边来表示。从任一顶点出发用广度优先对图进行遍历,统计所有长度不超过7的顶点
图的存储结构
图的逻辑结构:多对多
邻接矩阵:借助二维数组来表示图的元素间的关系
链式存储结构:由于图中顶点多对多的特性,很难确定需要多少个指针域,因此有如下几个常用的链式存储方式:邻接表,邻接多重表,十字链表
1.邻接矩阵
1.1无向图的邻接矩阵
-
建立一个顶点表(记录各个顶点信息) 和一个邻接矩阵表(表示各个顶点之间的关系)
-
设图 A = (V, E)有 n 个顶点,则顶点表表示为:

-
图的邻接矩阵表是一个二维数组 A.arcs[n][n], 定义为

如果存在一条边 属于A中的边,则存在该边,记为1 (自身与自身无边)
eg:

PS: “邻接”,相邻且相接
-
1.特点:无向图的邻接矩阵是对称矩阵,且对角线上元素为0,因为当有一个顶点到另一个顶点存在边时,另一个顶点到该顶点也一定存在一条边
2.统计某一个顶点的度是多少只需遍历该顶点对应的数组,计算1的次数即可,即顶点 i 的度 = 第i行(列)中 1 的个数
3.特别的:无向完全图的邻接矩阵中,对角元素为0,其余均为1
1.2有向图的邻接矩阵
有向图存在方向,因此只有当存在当前顶点发出的弧时,才记为1 (和无向图的邻接矩阵一样,自身与自身无弧)

在有向图的邻接矩阵中:
- 第 i 行:表示以结点Vi为尾的弧(即出度边)
- 第 i 列:表示以结点Vi为头的弧(即入度边)
1.有向图的邻接矩阵的特点:对角线元素为0,但不一定是对称矩阵
2.对有向图的邻接矩阵而言:
- 顶点 i 的出度 = 第 i 行的元素之和
- 顶点 i 的入度 = 第 i 列的元素之和
- 顶点 i 的度 = 第 i 行元素之和 + 第 i 列元素之和
3.特别的:有向完全图的对角线元素全0,取余元素全1
1.3网的邻接矩阵
网即有权图
网的邻接矩阵定义为:
如果存在边 / 弧属于VR图,则邻接矩阵记录该条边的权值Wij;否则记录INF
1.3.1有向网的邻接矩阵
1.4 无向网的存储结构
两个数组 :顶点表和邻接矩阵
//最大顶点数
#define MAX_VEX_NUM 100
//假的无穷大,填补邻接矩阵中没有邻接关系的点
#define MAX_INT 2147483647
typedef struct Graph
{
//顶点表
顶点数据类型 vexs[MAX_VEX_NUM];
//邻接矩阵表
边的类型 arcs[MAX_VEX_NUM][MAX_VEN_NUM];
//顶点数和边数
int vexNum, arcNum;
}AMGraph;
1.4.1采用邻接矩阵创建无向网
- 输入总顶点数和总边数
- 依次输入顶点的信息存入顶点表
- 初始化邻接矩阵,因为是在网当中,所以使每个权值初始化为最大值
- 构造邻接矩阵
1.4.1.1实现
构造
void creatGraph(AMGraph* graph)
{
printf("请输入顶点数和边数:");
//1.顶点数和边数
scanf("%d %d", &(graph->vexNum), &(graph->arcNum));
setbuf(stdin, NULL);
putchar('\n');
//顶点数据
for (int i = 0; i < graph->vexNum; i++)
{
printf("请输入第%d个顶点数据:", i + 1);
scanf("%c", &(graph->vexs[i]));
setbuf(stdin, NULL);
putchar('\n');
}
//3.初始化边, n个顶点就有
for (int i = 0; i < graph->vexNum; i++)
{
for (int j = 0; j < graph->vexNum; j++)
{
if(i!=j)
graph->arcs[i][j] = MAX_INT;
else
graph->arcs[i][j] = 0;
}
}
//4.构造邻接矩阵
for (int i = 0; i < graph->arcNum; i++)
{
char v1, v2;
int weight;
printf("请输入第%d条边依附的顶点及权值:", i+1);
scanf("%c %c %d", &v1, &v2, &weight);
setbuf(stdin, NULL);
putchar('\n');
//找到边依附的两个顶点,记录下标,就是在邻接矩阵中的位置
int i = findVex(graph, v1);
int j = findVex(graph, v2);
//如果没查找到顶点或该顶点已经有依附的边
if (i == -1 || j == -1 || graph->arcs[i][j] != MAX_INT)
{
printf("不存在该顶点或该顶点已有依附的边\n");
exit(-1);
}
//对称赋值
graph->arcs[i][j] = weight;
graph->arcs[j][i] = weight;
}
}
查找顶点是否存在
int findVex(AMGraph* graph, char vex)
{
for (int i = 0; i < graph->vexNum; i++)
{
if (vex == graph->vexs[i])
{
return i;
}
}
return -1;
}
1.4.2邻接矩阵创建无向图和有向网
无向图:和无向网区别就是无向图的边不存在权值,因此初始化邻接矩阵时边均为0,构造邻接矩阵时,存在的边为1
有向网:有向网的邻接矩阵不一定时对称矩阵,因此仅需为graph->arcs[i][j]赋值,而不需要在对称元素上赋值
邻接矩阵不便于增加和删除顶点;当边数较少时比较浪费空间
2.邻接表
2.1无向图的邻接表

-
顶点:按编号顺序将顶点存放在一维数组中,一维数组中每个元素都有指针域和数据域

-
边:用链表存储关联了同一顶点的所有边;每个结点有两个数据
adjvex为邻接顶点,nextarc为下一条边;eg:
代表v1有两条边,分别邻接的是在顶点数组中下标为3的顶点和下标为1的顶点当为无向网时,可以给表结点增加数据域以存放权值
无向图邻接表的特点:
- 邻接表不唯一,关联了同一顶点的边在链表中的位置可以任意
- 每一条边会在一个邻接表中出现2次,因此在无向图的邻接表当中,若有 n 个顶点,e 条边, 则其邻接表需要 n 个头结点和 2e 个表结点
- 用邻接表表示的无向图中顶点vi的度为第 i 个单链表中的结点数
2.1.1实现
实现步骤:
- 输入顶点数和边数
- 建立顶点表,依次输入顶点数据,并初始化顶点表中每个顶点的指针域为NULL
- 创建临邻接表
- 依次输入每条边依附的两个顶点( 起点,终点 )
- 确定两个顶点的下标 i 和 j ,建立两个边结点,一个结点的head存放起点的下标,另一个存放终点的下标
- 将存放起点下标的边结点链接到对应的终点顶点上,存放终点下标的边结点链接到对应的起点顶点上
- 结构
//边结构
typedef struct GraphNode
{
int head;
struct GraphNode* nextArc;
}gNode;
//顶点表
typedef struct Head
{
char data;
gNode* firstArc;
}head;
//图的基本结构
typedef struct ALGraph
{
head gHead[MAX_VEX];
int vexNum, arcNum; //顶点数和边数
}ALgraph;
- 构造
void creatGraph(ALgraph* graph)
{
printf("请输入顶点数和边数: ");
scanf("%d %d", &(graph->vexNum), &(graph->arcNum));
setbuf(stdin, NULL);
putchar('\n');
//顶点赋值
for (int i = 0; i < graph->vexNum; i++)
{
char c;
printf("请输入第%d个顶点的数据:", i+1);
scanf("%c", &c);
graph->gHead[i].data = c;
graph->gHead[i].firstArc = NULL;
setbuf(stdin, NULL);
putchar('\n');
}
//边结点链接
for (int i = 0; i < graph->arcNum; i++)
{
char v1, v2;
printf("请输入第%d条边依附的结点:", i + 1);
scanf("%c %c", &v1, &v2);
setbuf(stdin, NULL);
int i = indexOfVex(graph, v1);
int j = indexOfVex(graph, v2);
//生成两个结点,因为一条边会关联两个顶点,两个顶点都会链接上这条边
//只是不同顶点对应的边结点的head值不一样
gNode* g = (gNode*)malloc(sizeof(gNode));
g->head = j;
g->nextArc = NULL;
gNode* g2 = (gNode*)malloc(sizeof(gNode));
g2->head = i;
g2->nextArc = NULL;
/*头插,在对应的关联边链表中进行插入
如果用尾插法,需要记录上一次的尾结点,但上一次的尾指针所指向的边结点
不一定就在当次循环中找到的顶点的边链表当中
例如:1. a b a-->1 b-->0 pre-->1
2. b c b-->0 b由于在上次时已经有长子了
而pre此时还是指向a的长子1,pre-->next = g的话,就会变成 a-->1-->2 b-->0,这显然不是我们想要的*/
//链接到对应顶点,存终点下标的链接到起点顶点,存起点下标的链接到终点顶点
g->nextArc = graph->gHead[i].firstArc;
graph->gHead[i].firstArc = g;
g2->nextArc = graph->gHead[j].firstArc;
graph->gHead[j].firstArc = g2;
}
}
2.2有向图的邻接表

有向图的邻接表创建与无向图的邻接表创建区别仅在于:在构造有向图时,由于弧是有方向的,因此只需构造一条出度边依附在出度点上即可,该边的邻接点域值head存储入度点的下标;而在无向图中每条边需要保存两次;因此邻接表表示的无向图空间复杂度记为O(n+2e),邻接表表示的有向图空间复杂度为O(n+e)
2.2.1实现
void creatGraph(ALgraph* graph)
{
printf("请输入顶点数和边数: ");
scanf("%d %d", &(graph->vexNum), &(graph->arcNum));
setbuf(stdin, NULL);
putchar('\n');
//顶点赋值
for (int i = 0; i < graph->vexNum; i++)
{
char c;
printf("请输入第%d个顶点的数据:", i+1);
scanf("%c", &c);
graph->gHead[i].data = c;
graph->gHead[i].firstArc = NULL;
setbuf(stdin, NULL);
putchar('\n');
}
//边结点链接
for (int i = 0; i < graph->arcNum; i++)
{
char v1, v2;
printf("请输入第%d条边依附的结点:"

本文详细介绍了图的定义与基本术语,包括有向图、无向图、完全图、稀疏图和稠密图的概念。重点讨论了图的存储结构,如邻接矩阵、邻接表、十字链表和邻接多重表,以及它们各自的特点和适用场景。此外,还阐述了图的遍历方法,包括深度优先搜索(DFS)和广度优先搜索(BFS),以及这两种搜索算法的时间复杂度分析。最后,文章提到了图的应用,如最小生成树的Prim算法和Kruskal算法,以及最短路径问题的Dijkstra算法和Floyd算法。

最低0.47元/天 解锁文章
2901

被折叠的 条评论
为什么被折叠?



