文章目录
1.图的基本概念
-
图的定义
图G由顶点集V和边集E组成,记为 G = ( V , E ) G=(V,E) G=(V,E)
∣ V ∣ |V| ∣V∣表示图G中顶点的个数,也是图G的阶;
∣ E ∣ |E| ∣E∣表示图G中边的条数。
- 注:线性表和树可以为空,但图不可以为空,即V一定是非空集,但边集E可以为空。
-
有向图
E是有向边的集合时,则G为有向图。
弧是顶点的有序对,记为 < v , w > <v,w> <v,w>,其中v和w是顶点,v是弧尾,w是弧头。
G = ( V , E ) V = { 1 , 2 , 3 } E = { < 1 , 2 > , < 2 , 1 > , < 2 , 3 > } G=(V,E)\\ V=\{1,2,3\}\\ E=\{<1,2>,<2,1>,<2,3>\} G=(V,E)V={1,2,3}E={<1,2>,<2,1>,<2,3>}
注: < v , w > ! = < w , v > <v,w>!=<w,v> <v,w>!=<w,v> -
无向图
E是无向边的集合时,则G为无向图。
弧是顶点的无序对,记为 ( v , w ) (v,w) (v,w),其中v和w是顶点。
G = ( V , E ) V = { 1 , 2 , 3 } E = { ( 1 , 2 ) , ( 1 , 3 ) , ( 2 , 3 ) } G=(V,E)\\ V=\{1,2,3\}\\ E=\{(1,2),(1,3),(2,3)\} G=(V,E)V={1,2,3}E={(1,2),(1,3),(2,3)}
注: ( v , w ) = = ( w , v ) (v,w)==(w,v) (v,w)==(w,v) -
简单图、多重图
-
简单图:1.不存在重复边;2.不存在顶点到自身的边。
-
多重图:1.两个结点边数多于一条;2.允许顶点和自身关联。
-
-
顶点的度、入度、出度
-
对于无向图
顶点v的度:是指依附于该顶点的边的条数,记为 T D ( v ) TD(v) TD(v)。
-
对于有向图
入度:以顶点v为终点的有向边的数目,记为 I D ( v ) ID(v) ID(v)。
出度:以顶点v为起点的有向边的数目,记为 O D ( v ) OD(v) OD(v)。
顶点的度:等于顶点v入度和出度之和,即 I D ( v ) + O D ( v ) ID(v)+OD(v) ID(v)+OD(v)。
在有向图中,入度之和=出度之和=边的条数
-
-
顶点-顶点的关系描述
-
路径
顶点 v p v_p vp到顶点 v q v_q vq之间的一条路径是指顶点序列。
-
回路/环
第一个和最后一个结点相同的路径。
-
简单路径
在路径序列中,顶点不重复出现的路径。
-
简单回路
除了第一个和最后一个顶点,其余顶点不重复出现。
-
路径长度
路径上边的数目。
-
点到点的距离
u到v的最短路径;如果两个点间没有路径,则记距离为无穷。
-
连通
无向图中从顶点v到w有路径存在,则成v和w连通。
-
强连通
有向图中,v到w和w到v都有路径,则称这两个顶点是强连通的。
-
-
连通图、强连通图
-
连通图
无向图中任意两个顶点连通。
注:对n个顶点的无向图G,
若G是连通图,则最少有 n − 1 n-1 n−1条边。
若G是非连通图,则最多边数 C n − 1 2 C^2_{n-1} Cn−12。
-
强连通图
有向图中任意两个顶点都强连通。
注:对n个顶点的有向图G,
若G是强连通图,则最少有n条边(形成回路)。
-
-
子图、生成子图
- 子图:取原图边和顶点的子集构成的图。
- 生成子图:顶点都取,边取子集构成的图。
-
连通分量
无向图中的极大连通子图称为连通分量。
即除了连通外,尽可能多的包含顶点和边。
-
强连通分量
有向图中的极大强连通子图称为有向图的强连通分量。
即子图必须强连通,同时保留尽可能多的边。
-
生成树
连通图的生成树是包含图中全部顶点的一个极小连通子图。
极小:边尽量小,但保持连通。
-
注:若图中有n个顶点,则生成树有n-1条边。
对生成树而言,若砍去它的一条边,则变成非连通图,若加上一条则形成回路。
-
-
生成森林
在非连通图中,连通分量的生成树构成了非连通图的生成森林。
-
边的权、带权图
- 边的权:边上赋予的数值。
- 带权图/网:边上带权值的图/网。
- 带权路径长度:一条路径上权值之和。
-
特殊形态的图
-
无向完全图:
无向图中任意两个顶点都存在边。
最多边数为 C n 2 C^2_n Cn2,即 n ( n − 1 ) / 2 n(n-1)/2 n(n−1)/2。
-
有向完全图:
有向图中任意两个顶点都存在方向相反的两条边。
最多边数为 2 C n 2 2C^2_n 2Cn2,即 n ( n − 1 ) n(n-1) n(n−1)。
-
稀疏图、稠密图
一个边少,一个边多。
-
树
不存在回路且连通的无向图。
-
-
重点小结
- 对于n个顶点的无向图G,
• 所有顶点的度之和 = 2 ∣ E ∣ 所有顶点的度之和=2|E| 所有顶点的度之和=2∣E∣。
• 若G是连通图,则最少有 n-1 条边(树),
若 |E|>n-1,则一定有回路。
• 若G是非连通图,则最多可能有 C n − 1 2 C^2_{n-1} Cn−12 条边
• 无向完全图共有 C n 2 C^2_n Cn2条边。
- 对于n个顶点的有向图G,
• 所有顶点的 出度之和 = 入度之和 = ∣ E ∣ 出度之和=入度之和=|E| 出度之和=入度之和=∣E∣
• 所有顶点的度之和 = 2 ∣ E ∣ 所有顶点的度之和=2|E| 所有顶点的度之和=2∣E∣
• 若G是强连通图,则最少有 n 条边(形成回路)
• 有向完全图共有 2 C n 2 2C^2_n 2Cn2条边
2.图的存储
2.1 邻接矩阵
-
不带权值的邻接矩阵法
用二位数组存储边的信息,两顶点相连为1,不相连为0。
#define MaxVertexNum 100 typedef struct{ char Vex[MaxVertexNum]; //顶点表 int Edge[MaxVertexNum][MaxVertexNum]; //邻接矩阵,边表 int vexnum,arcnum; //图当前顶点数/弧数 }MGraph;
-
求度
-
无向图
第i个结点的度=第i行(或第i列)的非零元素个数。
-
有向图
第i个结点的出度=第i行的非零元素个数
第i个结点的入度=第i列的非零元素个数
第i个结点的度=第i行第i列的非零元素个数之和
-
-
存储带权图的邻接矩阵法
有权值填权值,没权值则填无穷或0。
#define MaxVertexNum 100 #define INFINITY //最大的int值 typedef char VertexType; //顶点的数据类型 typedef int EdgeType; //带权图中边上权值的数据类型 typedef struct{ VertexType Vex[MaxVertexNum]; //顶点表 EdgeType Edge[MaxVertexNum][MaxVertexNum]; //邻接矩阵,边表 int vexnum,arcnum; //图当前顶点数/弧数 }MGraph;
-
邻接矩阵法的性能分析
- 空间复杂度: O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2) ——只和顶点数相关,和实际的边数无关。
- 适用于存储稠密图。
-
邻接矩阵的性质
设图G的邻接矩阵为A(矩阵元素0/1),则 A n A^n An的元素 A n [ i ] [ j ] A^n[i][j] An[i][j]等于由顶点i到j的长度为n的路径的数目。
2.2 邻接表
-
定义——顺序+链式存储
//顶点 typedef struct VNode{ VertexType data; //顶点信息 ArcNode *first; //第一条边 }VNode,AdjList[MaxVertexNum]; //用邻接表存储图 typedef struct{ AdjList vertices; int vexnum,arcnum; }ALGraph; //边/弧 typedef struct ArcNode{ int adjvex; //边指向哪个结点 struct ArcNode *next; //指向下一条弧的指针 //InfoType info //边权值 }ArcNode;
-
性能分析
-
对无向图,边结点的数量是 2 ∣ E ∣ 2|E| 2∣E∣,整体空间复杂度为 O ( ∣ V ∣ + 2 ∣ E ∣ ) O(|V|+2|E|) O(∣V∣+2∣E∣) —— 每个结点存储一个空间,每条边在其连接的两个结点上分别存储一次,即每条边存储两次。
-
对有向图,边结点数量是 ∣ E ∣ |E| ∣E∣,整体空间复杂度为 O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)。
-
-
注:邻接矩阵表示法是唯一的,但图的邻接表表示法不唯一(∵孩子的链接顺序不唯一)
-
邻接表和邻接矩阵的对比
2.3 十字链表
-
只存储有向图
-
空间复杂度: O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)
-
如何找到指定顶点的所有出边?——顺着绿色路线
如何找到指定顶点的所有入边?——顺着橙色路线
-
好处:
解决了邻接矩阵空间复杂度太高的问题,也解决邻接表找入边不方便必须遍历的问题。
2.4 邻接多重表
-
只存储无向图
可方便删除结点和边
-
空间复杂度: O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)
-
好处:
解决了邻接矩阵空间复杂度太高的问题,也解决邻接表删除边和结点不方便的问题。
2.5 四种存储方式的对比
邻接矩阵 | 邻接表 | 十字链表 | 邻接多重表 | |
---|---|---|---|---|
空间复杂度 | $O( | V | ^2)$ | 无向图$O( |
适用于 | 稠密图 | 稀疏图 | 只存有向图 | 只存无向图 |
表示方法 | 唯一 | 不唯一 | 不唯一 | 不唯一 |
计算度/出度/入度 | 必须遍历对应行或列 | 计算有向图的度和入度不方便,其余方便。 | ||
找相邻的边 | 必须遍历对应行和列 | 找有向图入边不方便,其余方便 | 很方便 | 很方便 |
删除边和顶点 | 删除边很方便,删除顶点需要大量移动数据 | 都不方便 | 很方便 | 很方便 |
3.图的基本操作
3.1 基本操作
函数名 | 函数作用 | 邻接矩阵 | 邻接表 |
---|---|---|---|
Adjacent(G,x,y) | 判断图G是否存在边<x,y>或(x,y) |
O
(
1
)
O(1)
O(1) 很方便,只需判断G[x][y] 是否为0。 | O ( 1 ) O(1) O(1)~ O ( V ) O(V) O(V) 要遍历所有边。 |
Neighbors(G,x) | 列出图G中与结点x相邻的边 | $O( | V |
InsertVertex(G,x) | 在图G中插入顶点x | O ( 1 ) O(1) O(1) 在矩阵末尾插入信息 | O ( 1 ) O(1) O(1) 在表末尾插入信息 |
DeleteVertex(G,x) | 从图G中删除顶点x | $O( | V |
AddEdge(G,x,y) | 若无向边(x,y)或有向边<x,y>不存在,则向图G中添加该边 | O ( 1 ) O(1) O(1) 直接在矩阵中写1 | O ( 1 ) O(1) O(1) 用头插法把新边的信息插入到链表中 |
RemoveEdge(G,x,y) | 若(x,y)或<x,y>存在,则删除该边 | O ( 1 ) O(1) O(1) 直接在矩阵中写0 | O ( 1 ) O(1) O(1)~$O( |
FirstNeighbor(G,x) | 求图G中顶点x的第一个邻接点。有则返回顶点号,无则返回-1。 | O ( 1 ) O(1) O(1)~$O( | V |
NextNeighbor(G,x,y) | 假设图G中顶点y是顶点x的一个邻接点,返回除y之外顶点x的下一个邻接点顶点号,如果没有则返回-1 | O ( 1 ) O(1) O(1)~$O( | V |
Get_edge_value(G,x,y) | 获取权值 | O ( 1 ) O(1) O(1) | O ( 1 ) O(1) O(1)~$O( |
Set_edge_value(G,x,y) | 设置权值 | O ( 1 ) O(1) O(1) | O ( 1 ) O(1) O(1)~$O( |
3.2 邻接表代码
#include <stdio.h>
#include <stdlib.h>
#define MaxVertexNum 100 // 最大顶点数定义为100
#define INF 9999 // 代表无穷大的定义为9999
// 边结点的定义
typedef struct ArcNode {
int adjvex; // 边指向哪个结点
int weight; // 边权值
struct ArcNode *next; // 指向下一条弧的指针
} ArcNode;
// 顶点结点的定义
typedef struct VNode {
int data; // 顶点信息
ArcNode *first; // 第一条边
} VNode, AdjList[MaxVertexNum]; // 邻接表的定义
// 图的定义
typedef struct {
AdjList vertices; // 顶点数组
int vexnum, arcnum; // 顶点数和边数
} ALGraph;
// 初始化邻接表图
void InitALGraph(ALGraph *G, int vexnum, int arcnum) {
G->vexnum = vexnum; // 初始化顶点数
G->arcnum = arcnum; // 初始化边数
for (int i = 0; i < vexnum; i++) { // 遍历顶点数组
G->vertices[i].data = i; // 顶点信息为i
G->vertices[i].first = NULL; // 第一条边为空
}
}
// 添加有向图的边
void AddEdgeDirectedALGraph(ALGraph *G, int v1, int v2, int weight) {
ArcNode *arcNode = (ArcNode *)malloc(sizeof(ArcNode)); // 分配边结点内存
arcNode->adjvex = v2; // 边指向v2
arcNode->weight = weight; // 边权值为weight
arcNode->next = G->vertices[v1].first; // 将边插入到顶点v1的边链表中
G->vertices[v1].first = arcNode;
}
// 添加无向图的边
void AddEdgeUndirectedALGraph(ALGraph *G, int v1, int v2, int weight) {
// 添加v1到v2的边
ArcNode *arcNode1 = (ArcNode *)malloc(sizeof(ArcNode)); // 分配边结点内存
arcNode1->adjvex = v2; // 边指向v2
arcNode1->weight = weight; // 边权值为weight
arcNode1->next = G->vertices[v1].first; // 将边插入到顶点v1的边链表中
G->vertices[v1].first = arcNode1;
// 添加v2到v1的边
ArcNode *arcNode2 = (ArcNode *)malloc(sizeof(ArcNode)); // 分配边结点内存
arcNode2->adjvex = v1; // 边指向v1
arcNode2->weight = weight; // 边权值为weight
arcNode2->next = G->vertices[v2].first; // 将边插入到顶点v2的边链表中
G->vertices[v2].first = arcNode2;
}
// 打印邻接表图
void PrintALGraph(ALGraph G) {
for (int i = 0; i < G.vexnum; i++) { // 遍历顶点数组
printf("%d -> ", G.vertices[i].data); // 打印顶点信息
ArcNode *p = G.vertices[i].first; // 获取顶点i的第一条边
while (p != NULL) { // 遍历顶点i的边链表
printf("(%d, %d) ", p->adjvex, p->weight); // 打印边的信息
p = p->next; // 指向下一条边
}
printf("\n");
}
}
// 判断两个顶点之间是否有边相连
int Adjacent(ALGraph G, int x, int y) {
ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
while (p != NULL) { // 遍历顶点x的边链表
if (p->adjvex == y) // 如果边的另一端是y
return 1; // 返回1,表示存在边<x, y>
p = p->next; // 指向下一条边
}
return 0; // 遍历完所有边仍未找到,返回0,表示不存在边<x, y>
}
// 打印顶点x的邻居顶点
void Neighbors(ALGraph G, int x) {
ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
printf("Neighbors of vertex %d: ", x); // 打印提示信息
while (p != NULL) { // 遍历顶点x的边链表
printf("%d ", p->adjvex); // 打印邻居顶点
p = p->next; // 指向下一条边
}
printf("\n");
}
// 插入顶点
void InsertVertex(ALGraph *G, int x) {
G->vertices[G->vexnum].data = x; // 在最后一个位置插入顶点x的信息
G->vertices[G->vexnum].first = NULL; // 边链表为空
G->vexnum++; // 顶点数加一
}
// 删除顶点
void DeleteVertex(ALGraph *G, int x) {
for (int i = 0; i < G->vexnum; i++) { // 遍历顶点数组
ArcNode *p = G->vertices[i].first; // 获取顶点i的第一条边
ArcNode *pre = NULL; // 初始化前一个边结点为NULL
while (p != NULL) { // 遍历顶点i的边链表
if (p->adjvex == x) { // 如果边的另一端是x
if (pre == NULL) // 如果是第一条边
G->vertices[i].first = p->next; // 将顶点i的第一条边指向下一条边
else
pre->next = p->next; // 将前一条边的next指针指向下一条边
free(p); // 释放删除的边结点的内存
break; // 结束循环
}
pre = p; // 更新前一个边结点
p = p->next; // 指向下一条边
}
}
// 删除顶点x
for (int i = x; i < G->vexnum - 1; i++) // 从顶点x开始,将后面的顶点信息依次往前移动
G->vertices[i] = G->vertices[i + 1]; // 将后一个顶点信息覆盖前一个顶点信息
G->vexnum--; // 顶点数减一
}
// 添加边
void AddEdge(ALGraph *G, int x, int y, int weight) {
if (!Adjacent(*G, x, y)) { // 如果两个顶点之间不存在边相连
AddEdgeUndirectedALGraph(G, x, y, weight); // 添加边<x, y>
}
}
// 删除边
void RemoveEdge(ALGraph *G, int x, int y) {
if (Adjacent(*G, x, y)) { // 如果两个顶点之间存在边相连
ArcNode *p = G->vertices[x].first; // 获取顶点x的第一条边
ArcNode *pre = NULL; // 初始化前一个边结点为NULL
while (p != NULL) { // 遍历顶点x的边链表
if (p->adjvex == y) { // 如果边的另一端是y
if (pre == NULL) // 如果是第一条边
G->vertices[x].first = p->next; // 将顶点x的第一条边指向下一条边
else
pre->next = p->next; // 将前一条边的next指针指向下一条边
free(p); // 释放删除的边结点的内存
break; // 结束循环
}
pre = p; // 更新前一个边结点
p = p->next; // 指向下一条边
}
// 如果是无向图,还需要删除另一个方向的边
if (G->vertices[y].first != NULL) { // 如果顶点y有边相连
p = G->vertices[y].first; // 获取顶点y的第一条边
pre = NULL; // 初始化前一个边结点为NULL
while (p != NULL) { // 遍历顶点y的边链表
if (p->adjvex == x) { // 如果边的另一端是x
if (pre == NULL) // 如果是第一条边
G->vertices[y].first = p->next; // 将顶点y的第一条边指向下一条边
else
pre->next = p->next; // 将前一条边的next指针指向下一条边
free(p); // 释放删除的边结点的内存
break; // 结束循环
}
pre = p; // 更新前一个边结点
p = p->next; // 指向下一条边
}
}
}
}
// 获取顶点x的第一个邻居顶点
int FirstNeighbor(ALGraph G, int x) {
if (G.vertices[x].first != NULL) // 如果顶点x有边相连
return G.vertices[x].first->adjvex; // 返回第一个邻居顶点的信息
else
return -1; // 否则返回-1,表示没有邻居顶点
}
// 获取顶点x的邻居顶点y的下一个邻居顶点
int NextNeighbor(ALGraph G, int x, int y) {
ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
while (p != NULL && p->adjvex != y) // 遍历顶点x的边链表,直到找到邻居顶点y
p = p->next; // 指向下一条边
if (p != NULL && p->next != NULL) // 如果邻居顶点y后面还有邻居顶点
return p->next->adjvex; // 返回邻居顶点y的下一个邻居顶点的信息
else
return -1; // 否则返回-1,表示没有下一个邻居顶点
}
// 获取边<x, y>的权值
int Get_edge_value(ALGraph G, int x, int y) {
ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
while (p != NULL) { // 遍历顶点x的边链表
if (p->adjvex == y) // 如果边的另一端是y
return p->weight; // 返回边的权值
p = p->next; // 指向下一条边
}
return INF; // 遍历完所有边仍未找到,返回无穷大,表示不存在边<x, y>
}
// 设置边<x, y>的权值为v
void Set_edge_value(ALGraph *G, int x, int y, int v) {
ArcNode *p = G->vertices[x].first; // 获取顶点x的第一条边
while (p != NULL) { // 遍历顶点x的边链表
if (p->adjvex == y) { // 如果边的另一端是y
p->weight = v; // 设置边的权值为v
break; // 结束循环
}
p = p->next; // 指向下一条边
}
}
int main() {
// 创建有向图的邻接表表示
ALGraph directedALGraph; // 定义有向图
InitALGraph(&directedALGraph, 5, 6); // 初始化有向图
AddEdgeDirectedALGraph(&directedALGraph, 0, 1, 1); // 添加边
AddEdgeDirectedALGraph(&directedALGraph, 0, 2, 2); // 添加边
AddEdgeDirectedALGraph(&directedALGraph, 1, 2, 3); // 添加边
AddEdgeDirectedALGraph(&directedALGraph, 2, 3, 4); // 添加边
AddEdgeDirectedALGraph(&directedALGraph, 3, 4, 5); // 添加边
AddEdgeDirectedALGraph(&directedALGraph, 4, 0, 6); // 添加边
// 创建无向图的邻接表表示
ALGraph undirectedALGraph; // 定义无向图
InitALGraph(&undirectedALGraph, 5, 6); // 初始化无向图
AddEdgeUndirectedALGraph(&undirectedALGraph, 0, 1, 1); // 添加边
AddEdgeUndirectedALGraph(&undirectedALGraph, 0, 2, 2); // 添加边
AddEdgeUndirectedALGraph(&undirectedALGraph, 1, 2, 3); // 添加边
AddEdgeUndirectedALGraph(&undirectedALGraph, 2, 3, 4); // 添加边
AddEdgeUndirectedALGraph(&undirectedALGraph, 3, 4, 5); // 添加边
AddEdgeUndirectedALGraph(&undirectedALGraph, 4, 0, 6); // 添加边
printf("\nDirected Graph (Adjacency List):\n");
PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
printf("\nUndirected Graph (Adjacency List):\n");
PrintALGraph(undirectedALGraph); // 打印无向图的邻接表表示
// 测试基本操作
printf("\nTest Basic Operations:\n");
printf("Adjacent(directedALGraph, 0, 1): %d\n", Adjacent(directedALGraph, 0, 1)); // 测试两个顶点之间是否有边相连
printf("Adjacent(directedALGraph, 0, 3): %d\n", Adjacent(directedALGraph, 0, 3)); // 测试两个顶点之间是否有边相连
printf("Adjacent(undirectedALGraph, 0, 1): %d\n", Adjacent(undirectedALGraph, 0, 1)); // 测试两个顶点之间是否有边相连
printf("Neighbors(directedALGraph, 0): ");
Neighbors(directedALGraph, 0); // 打印顶点0的邻居顶点
printf("Neighbors(undirectedALGraph, 0): ");
Neighbors(undirectedALGraph, 0); // 打印顶点0的邻居顶点
InsertVertex(&directedALGraph, 5); // 插入顶点5
printf("After InsertVertex(directedALGraph, 5):\n");
PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
DeleteVertex(&directedALGraph, 5); // 删除顶点5
printf("After DeleteVertex(directedALGraph, 5):\n");
PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
AddEdge(&directedALGraph, 1, 4, 7); // 添加边
printf("After AddEdge(directedALGraph, 1, 4, 7):\n");
PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
RemoveEdge(&directedALGraph, 1, 4); // 删除边
printf("After RemoveEdge(directedALGraph, 1, 4):\n");
PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
printf("FirstNeighbor(undirectedALGraph, 2): %d\n", FirstNeighbor(undirectedALGraph, 2)); // 获取顶点2的第一个邻居顶点
printf("NextNeighbor(undirectedALGraph, 2, 3): %d\n", NextNeighbor(undirectedALGraph, 2, 3)); // 获取顶点2的邻居顶点3的下一个邻居顶点
printf("Get_edge_value(undirectedALGraph, 2, 3): %d\n", Get_edge_value(undirectedALGraph, 2, 3)); // 获取边
Set_edge_value(&undirectedALGraph, 2, 3, 10);
printf("After Set_edge_value(undirectedALGraph, 2, 3, 10):\n");
PrintALGraph(undirectedALGraph);
return 0;
}
3.3 邻接矩阵代码
#include <stdio.h>
#include <stdlib.h>
#define MaxVertexNum 100
#define INFINITY 9999 // 代表无穷大
// 邻接矩阵存储有向图和无向图
typedef struct {
char Vex[MaxVertexNum]; // 顶点表
int Edge[MaxVertexNum][MaxVertexNum]; // 邻接矩阵,边表
int vexnum, arcnum; // 图当前顶点数/弧数
int weight[MaxVertexNum][MaxVertexNum]; // 权值
} MGraph;
// 初始化邻接矩阵图
void InitMGraph(MGraph *G, int vexnum, int arcnum) {
G->vexnum = vexnum;
G->arcnum = arcnum;
for (int i = 0; i < vexnum; i++) {
for (int j = 0; j < vexnum; j++) {
G->Edge[i][j] = 0; // 初始化边为0,表示无连接
G->weight[i][j] = INFINITY; // 初始化权值为无穷大
}
}
}
// 添加有向图邻接矩阵的边
void AddEdgeDirectedMGraph(MGraph *G, int v1, int v2, int weight) {
G->Edge[v1][v2] = 1; // 有连接的边置为1
G->weight[v1][v2] = weight; // 设置权值
}
// 添加无向图邻接矩阵的边
void AddEdgeUndirectedMGraph(MGraph *G, int v1, int v2, int weight) {
G->Edge[v1][v2] = 1; // 有连接的边置为1
G->Edge[v2][v1] = 1; // 对称位置也置为1
G->weight[v1][v2] = weight; // 设置权值
G->weight[v2][v1] = weight; // 对称位置设置权值
}
// 打印邻接矩阵图
void PrintMGraph(MGraph G) {
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
printf("%d ", G.Edge[i][j]);
}
printf("\n");
}
}
// 判断图G是否存在边<x, y>或(x, y),并返回权值
int Adjacent(MGraph G, int x, int y) {
if (G.Edge[x][y] == 1 || G.Edge[y][x] == 1) {
return G.weight[x][y]; // 返回边的权值
} else {
return INFINITY; // 不存在边<x, y>或(x, y),返回无穷大
}
}
// 列出图G中与结点x邻接的边
void Neighbors(MGraph G, int x) {
printf("Neighbors of vertex %d: ", x);
for (int i = 0; i < G.vexnum; i++) {
if (G.Edge[x][i] == 1) {
printf("%d ", i);
}
}
printf("\n");
}
// 在图G中插入顶点x
void InsertVertex(MGraph *G, int x) {
if (G->vexnum < MaxVertexNum) {
G->Vex[G->vexnum] = x;
G->vexnum++;
} else {
printf("Exceeding maximum vertex number!\n");
}
}
// 从图G中删除顶点x
void DeleteVertex(MGraph *G, int x) {
if (x >= G->vexnum) {
printf("Vertex does not exist!\n");
return;
}
// 删除顶点x的边
for (int i = 0; i < G->vexnum; i++) {
G->Edge[i][x] = 0;
G->Edge[x][i] = 0;
G->weight[i][x] = INFINITY; // 删除边同时将权值设置为无穷大
G->weight[x][i] = INFINITY;
}
// 移动顶点表,覆盖被删除的顶点
for (int i = x; i < G->vexnum - 1; i++) {
G->Vex[i] = G->Vex[i + 1];
}
G->vexnum--;
}
// 若无向边(x, y)或有向边<x, y>不存在,则向图G中添加该边
void AddEdge(MGraph *G, int x, int y, int weight) {
if (G->Edge[x][y] == 0) {
G->Edge[x][y] = 1;
G->Edge[y][x] = 1; // 对于无向图,要同时设置对称位置
G->weight[x][y] = weight;
G->weight[y][x] = weight; // 对称位置设置权值
}
}
// 若无向边(x, y)或有向边<x, y>存在,则从图G中删除该边
void RemoveEdge(MGraph *G, int x, int y) {
if (G->Edge[x][y] == 1) {
G->Edge[x][y] = 0;
G->Edge[y][x] = 0; // 对于无向图,要同时设置对称位置
G->weight[x][y] = INFINITY;
G->weight[y][x] = INFINITY; // 对称位置设置权值为无穷大
}
}
// 求图G中顶点x的第一个邻接点,若有则返回顶点号,若x没有邻接点或图中不存在x,则返回-1
int FirstNeighbor(MGraph G, int x) {
for (int i = 0; i < G.vexnum; i++) {
if (G.Edge[x][i] == 1) {
return i;
}
}
return -1;
}
// 返回除y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1
int NextNeighbor(MGraph G, int x, int y) {
for (int i = y + 1; i < G.vexnum; i++) {
if (G.Edge[x][i] == 1) {
return i;
}
}
return -1;
}
// 获取图G中边(x, y)或<x, y>对应的权值
int Get_edge_value(MGraph G, int x, int y) {
return G.weight[x][y];
}
// 设置图G中边(x, y)或<x, y>对应的权值为v
void Set_edge_value(MGraph *G, int x, int y, int v) {
G->weight[x][y] = v;
G->weight[y][x] = v; // 对称位置设置权值
}
int main() {
// 创建有向图的邻接矩阵表示
MGraph directedMGraph;
InitMGraph(&directedMGraph, 5, 6);
AddEdgeDirectedMGraph(&directedMGraph, 0, 1, 2);
AddEdgeDirectedMGraph(&directedMGraph, 0, 2, 3);
AddEdgeDirectedMGraph(&directedMGraph, 1, 2, 4);
AddEdgeDirectedMGraph(&directedMGraph, 2, 3, 5);
AddEdgeDirectedMGraph(&directedMGraph, 3, 4, 6);
AddEdgeDirectedMGraph(&directedMGraph, 4, 0, 7);
// 创建无向图的邻接矩阵表示
MGraph undirectedMGraph;
InitMGraph(&undirectedMGraph, 5, 6);
AddEdgeUndirectedMGraph(&undirectedMGraph, 0, 1, 2);
AddEdgeUndirectedMGraph(&undirectedMGraph, 0, 2, 3);
AddEdgeUndirectedMGraph(&undirectedMGraph, 1, 2, 4);
AddEdgeUndirectedMGraph(&undirectedMGraph, 2, 3, 5);
AddEdgeUndirectedMGraph(&undirectedMGraph, 3, 4, 6);
AddEdgeUndirectedMGraph(&undirectedMGraph, 4, 0, 7);
// 打印结果
printf("Directed Graph (Adjacency Matrix):\n");
PrintMGraph(directedMGraph);
printf("\nUndirected Graph (Adjacency Matrix):\n");
PrintMGraph(undirectedMGraph);
// 测试基本操作
printf("\nTest Basic Operations:\n");
printf("Adjacent(directedMGraph, 0, 1): %d\n", Adjacent(directedMGraph, 0, 1));
printf("Adjacent(directedMGraph, 0, 3): %d\n", Adjacent(directedMGraph, 0, 3));
printf("Neighbors(directedMGraph, 0): ");
Neighbors(directedMGraph, 0);
printf("Neighbors(undirectedMGraph, 0): ");
Neighbors(undirectedMGraph, 0);
InsertVertex(&directedMGraph, 5);
printf("After InsertVertex(directedMGraph, 5):\n");
PrintMGraph(directedMGraph);
DeleteVertex(&directedMGraph, 5);
printf("After DeleteVertex(directedMGraph, 5):\n");
PrintMGraph(directedMGraph);
AddEdge(&directedMGraph, 1, 4, 8);
printf("After AddEdge(directedMGraph, 1, 4):\n");
PrintMGraph(directedMGraph);
RemoveEdge(&directedMGraph, 1, 4);
printf("After RemoveEdge(directedMGraph, 1, 4):\n");
PrintMGraph(directedMGraph);
printf("FirstNeighbor(undirectedMGraph, 2): %d\n", FirstNeighbor(undirectedMGraph, 2));
printf("NextNeighbor(undirectedMGraph, 2, 3): %d\n", NextNeighbor(undirectedMGraph, 2, 3));
printf("Get_edge_value(undirectedMGraph, 2, 3): %d\n", Get_edge_value(undirectedMGraph, 2, 3));
Set_edge_value(&undirectedMGraph, 2, 3, 10);
printf("After Set_edge_value(undirectedMGraph, 2, 3, 10):\n");
PrintMGraph(undirectedMGraph);
return 0;
}
4.图的遍历
4.1 广度优先遍历
-
树的广度优先遍历==层序遍历
-
图的广度优先遍历(BFS)
-
定义:先访问一个顶点,再访问与该结点相邻的结点,再访问这些相邻结点的相邻结点,依次循环直到遍历完。
-
要点:
1.找到与一个顶点相邻的所有顶点;
2.标记哪些顶点被访问过;
3.需要一个辅助队列。
-
注:
同一个图的邻接矩阵表示方法唯一,因此广度优先遍历序列唯一。
同一个图邻接表表示方式不唯一,因此广度优先遍历序列不唯一。
-
bool visited[MAX_VERTEX_NUM];//访问标记数组
//对图G进行广度优先遍历
void BFSTraverse(Graph G){
int i;
for(i=0;i<G.vexnum;++i)
visited[i]=FALSE; //访问标记数组初始化
InitQueue(Q); //初始化辅助队列
//从0号结点开始遍历
for(i=0;i<G.vexnum;++i){
if(!visited[i]) //对每个连通分量调用一次BFS
BFS(G,i); //vi未被访问,则从vi开始BFS
}
}
//广度优先遍历
void BFS(Graph G,int v){ //从顶点v出发,广度优先遍历图G
visit(v); //访问初始顶点v
visited[v]=TRUE; //对v做已访问标记
Enqueue(Q,v); //顶点v入队列Q
while(!isEmpty(Q)){
DeQueue(Q,v); //顶点v出队列
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
//检测v所有邻接点
if(!visited[w]){ //w为v的尚未访问的邻接顶点
visited(w); //访问w
visited[w]=TRUE; //标记w已被访问
EnQueue(Q,w); //顶点w入队
}
}
}
}
-
复杂度分析
-
邻接矩阵存储的图:
访问 ∣ V ∣ |V| ∣V∣个顶点需要 Q ( ∣ V ∣ ) Q(|V|) Q(∣V∣)的时间
查找每个顶点的邻接点都需要 O ( ∣ V ∣ ) O(|V|) O(∣V∣)的时间,而总共有 ∣ V ∣ |V| ∣V∣个顶点。
时间复杂度: O ( ∣ V 2 ∣ ) O(|V^2|) O(∣V2∣)
-
邻接表存储的图:
访问 ∣ V ∣ |V| ∣V∣个顶点需要 O ( ∣ V ∣ ) O(|V|) O(∣V∣)的时间
查找各个顶点的邻接点共需要 O ( ∣ E ∣ ) O(|E|) O(∣E∣)的时间
时间复杂度: O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)
-
-
广度优先生成树/森林
下图中标红的标明是第一次遍历到该结点的路径,该路径可生成树/森林:
∵ 邻接表存储的图表示方式不唯一,遍历序列、生成树也不唯一。
4.2 深度优先遍历
-
树的深度优先遍历 相当于树的先根遍历
-
图的深度优先遍历
1.访问一个结点a;
2.访问与该结点a相连的结点b;
3.继续访问相连结点b的相连结点,依次递归直到没有相连结点;
4.再取未被访问的结点开始重复1,2,3步的访问。
bool visited[MAX_VERTEX_NUM]; //访问标记数组
void DFSTraverse(Graph G){
for(v=0;v<G.vexnum;++v){
visited[v]=false;
}
for(v=0;v<G.vexnum;++v){ //从第0个结点开始遍历
if(!visited[v])
DFS(G,v);
}
}
void DFS(Graph G,int v){
visit(v); //访问顶点v
visited[v]=TRUE; //设已访问标记
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
if(!visited[w]){
DFS(G,w);
}
}
}
-
空间复杂度:
来自函数调用栈,最坏情况,递归深度为 O ( ∣ V ∣ ) O(|V|) O(∣V∣),最好情况 O ( 1 ) O(1) O(1)
-
时间复杂度=访问各结点所需时间+探索各条边所需的时间
-
邻接矩阵存储的图
访问 ∣ V ∣ |V| ∣V∣个顶点需要 O ( ∣ V ∣ ) O(|V|) O(∣V∣)的时间
查找每个顶点的邻接点都需要 O ( ∣ V ∣ ) O(|V|) O(∣V∣)的时间,而总共有 ∣ V ∣ |V| ∣V∣个顶点
时间复杂度= O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2)
-
邻接表存储的图
访问 ∣ V ∣ |V| ∣V∣个顶点需要 O ( ∣ V ∣ ) O(|V|) O(∣V∣)的时间
查找各个顶点的邻接点共需要 O ( ∣ E ∣ ) O(|E|) O(∣E∣)的时间
时间复杂度= O ( ∣ V ∣ + ∣ E ∣ ) O(|V|+|E|) O(∣V∣+∣E∣)
-
-
深度优先生成树/森林
由深度优先遍历确定的树。
邻接表存储的图表示方式不唯一,深度优先序列和深度优先生成树/森林也不唯一。
和广度优先生成树类似,第一次遍历到的边标红,并形成树/森林:
4.3 图的遍历与图的连通性
-
对无向图进行BFS/DFS遍历
调用BFS/DFS函数的次数=连通分量数
对于连通图,只调用1次BFS/DFS
-
对有向图进行BFS/DFS遍历
调用BFS/DFS函数的次数要具体分析。
若起始顶点到其他各顶点都有路径,则只需调用1次BFS/DFS函数。
对强连通图,从任一顶点出发都只需调用一次DFS/BFS函数。
*完整代码 邻接矩阵
#include <stdio.h>
#include <stdlib.h>
#define MaxVertexNum 100
#define INFINITY 9999 // 代表无穷大
// 邻接矩阵存储有向图和无向图
typedef struct {
char Vex[MaxVertexNum]; // 顶点表
int Edge[MaxVertexNum][MaxVertexNum]; // 邻接矩阵,边表
int vexnum, arcnum; // 图当前顶点数/弧数
int weight[MaxVertexNum][MaxVertexNum]; // 权值
} MGraph;
// 初始化邻接矩阵图
void InitMGraph(MGraph *G, int vexnum, int arcnum) {
G->vexnum = vexnum;
G->arcnum = arcnum;
for (int i = 0; i < vexnum; i++) {
for (int j = 0; j < vexnum; j++) {
G->Edge[i][j] = 0; // 初始化边为0,表示无连接
G->weight[i][j] = INFINITY; // 初始化权值为无穷大
}
}
}
// 添加有向图邻接矩阵的边
void AddEdgeDirectedMGraph(MGraph *G, int v1, int v2, int weight) {
G->Edge[v1][v2] = 1; // 有连接的边置为1
G->weight[v1][v2] = weight; // 设置权值
}
// 添加无向图邻接矩阵的边
void AddEdgeUndirectedMGraph(MGraph *G, int v1, int v2, int weight) {
G->Edge[v1][v2] = 1; // 有连接的边置为1
G->Edge[v2][v1] = 1; // 对称位置也置为1
G->weight[v1][v2] = weight; // 设置权值
G->weight[v2][v1] = weight; // 对称位置设置权值
}
// 打印邻接矩阵图
void PrintMGraph(MGraph G) {
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
printf("%d ", G.Edge[i][j]);
}
printf("\n");
}
}
// 判断图G是否存在边<x, y>或(x, y),并返回权值
int Adjacent(MGraph G, int x, int y) {
if (G.Edge[x][y] == 1 || G.Edge[y][x] == 1) {
return G.weight[x][y]; // 返回边的权值
} else {
return INFINITY; // 不存在边<x, y>或(x, y),返回无穷大
}
}
// 列出图G中与结点x邻接的边
void Neighbors(MGraph G, int x) {
printf("Neighbors of vertex %d: ", x);
for (int i = 0; i < G.vexnum; i++) {
if (G.Edge[x][i] == 1) {
printf("%d ", i);
}
}
printf("\n");
}
// 在图G中插入顶点x
void InsertVertex(MGraph *G, int x) {
if (G->vexnum < MaxVertexNum) {
G->Vex[G->vexnum] = x;
G->vexnum++;
} else {
printf("Exceeding maximum vertex number!\n");
}
}
// 从图G中删除顶点x
void DeleteVertex(MGraph *G, int x) {
if (x >= G->vexnum) {
printf("Vertex does not exist!\n");
return;
}
// 删除顶点x的边
for (int i = 0; i < G->vexnum; i++) {
G->Edge[i][x] = 0;
G->Edge[x][i] = 0;
G->weight[i][x] = INFINITY; // 删除边同时将权值设置为无穷大
G->weight[x][i] = INFINITY;
}
// 移动顶点表,覆盖被删除的顶点
for (int i = x; i < G->vexnum - 1; i++) {
G->Vex[i] = G->Vex[i + 1];
}
G->vexnum--;
}
// 若无向边(x, y)或有向边<x, y>不存在,则向图G中添加该边
void AddEdge(MGraph *G, int x, int y, int weight) {
if (G->Edge[x][y] == 0) {
G->Edge[x][y] = 1;
G->Edge[y][x] = 1; // 对于无向图,要同时设置对称位置
G->weight[x][y] = weight;
G->weight[y][x] = weight; // 对称位置设置权值
}
}
// 若无向边(x, y)或有向边<x, y>存在,则从图G中删除该边
void RemoveEdge(MGraph *G, int x, int y) {
if (G->Edge[x][y] == 1) {
G->Edge[x][y] = 0;
G->Edge[y][x] = 0; // 对于无向图,要同时设置对称位置
G->weight[x][y] = INFINITY;
G->weight[y][x] = INFINITY; // 对称位置设置权值为无穷大
}
}
// 求图G中顶点x的第一个邻接点,若有则返回顶点号,若x没有邻接点或图中不存在x,则返回-1
int FirstNeighbor(MGraph G, int x) {
for (int i = 0; i < G.vexnum; i++) {
if (G.Edge[x][i] == 1) {
return i;
}
}
return -1;
}
// 返回除y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1
int NextNeighbor(MGraph G, int x, int y) {
for (int i = y + 1; i < G.vexnum; i++) {
if (G.Edge[x][i] == 1) {
return i;
}
}
return -1;
}
// 获取图G中边(x, y)或<x, y>对应的权值
int Get_edge_value(MGraph G, int x, int y) {
return G.weight[x][y];
}
// 设置图G中边(x, y)或<x, y>对应的权值为v
void Set_edge_value(MGraph *G, int x, int y, int v) {
G->weight[x][y] = v;
G->weight[y][x] = v; // 对称位置设置权值
}
void visit(int v){
printf("%d ",v);
}
// 定义队列结点
typedef struct QueueNode {
int data;
struct QueueNode* next;
} QueueNode;
// 定义队列
typedef struct {
QueueNode *front;
QueueNode *rear;
} Queue;
// 初始化队列
void InitQueue(Queue *Q) {
Q->front = Q->rear = NULL;
}
// 入队操作
void EnQueue(Queue *Q, int data) {
QueueNode *newNode = (QueueNode *)malloc(sizeof(QueueNode));
newNode->data = data;
newNode->next = NULL;
if (Q->rear == NULL) {
Q->front = Q->rear = newNode;
} else {
Q->rear->next = newNode;
Q->rear = newNode;
}
}
// 出队操作
int DeQueue(Queue *Q) {
if (Q->front == NULL)
return -1; // 队列为空
QueueNode *temp = Q->front;
int data = temp->data;
Q->front = Q->front->next;
if (Q->front == NULL)
Q->rear = NULL;
free(temp);
return data;
}
// 判断队列是否为空
int IsQueueEmpty(Queue Q) {
return Q.front == NULL;
}
// 广度优先遍历
void BFS(MGraph G, int v, bool visited[], Queue *Q) { // 从顶点v出发,广度优先遍历图G
visit(v); // 访问初始顶点v
visited[v] = true; // 对v做已访问标记
EnQueue(Q, v); // 顶点v入队列Q
while (!IsQueueEmpty(*Q)) {
int w;
w=DeQueue(Q); // 顶点v出队列
for (w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)) {
// 检测v所有邻接点
if (!visited[w]) { // w为v的尚未访问的邻接顶点
visit(w); // 访问w
visited[w] = true; // 标记w已被访问
EnQueue(Q, w); // 顶点w入队
}
}
}
}
// 广度优先遍历
// 对图G进行广度优先遍历
void BFSTraverse(MGraph G) {
bool visited[MaxVertexNum] = {false};
Queue Q;
InitQueue(&Q); // 初始化辅助队列
// 从0号结点开始遍历
for (int i = 0; i < G.vexnum; ++i) {
if (!visited[i]) // 对每个连通分量调用一次BFS
BFS(G, i, visited, &Q); // vi未被访问,则从vi开始BFS
}
}
// 深度优先遍历
void DFS(MGraph G, int v, bool visited[]) {
visit(v); // 访问顶点v
visited[v] = true; // 设已访问标记
for (int w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)) {
if (!visited[w]) {
DFS(G, w, visited);
}
}
}
void DFSTraverse(MGraph G) {
bool visited[MaxVertexNum] = {false};
for (int i = 0; i < G.vexnum; ++i) {
if (!visited[i]) {
DFS(G, i, visited);
}
}
}
int main() {
// 创建有向图的邻接矩阵表示
MGraph directedMGraph;
InitMGraph(&directedMGraph, 5, 6);
AddEdgeDirectedMGraph(&directedMGraph, 0, 1, 2);
AddEdgeDirectedMGraph(&directedMGraph, 0, 2, 3);
AddEdgeDirectedMGraph(&directedMGraph, 1, 2, 4);
AddEdgeDirectedMGraph(&directedMGraph, 2, 3, 5);
AddEdgeDirectedMGraph(&directedMGraph, 3, 4, 6);
AddEdgeDirectedMGraph(&directedMGraph, 4, 0, 7);
// 创建无向图的邻接矩阵表示
MGraph undirectedMGraph;
InitMGraph(&undirectedMGraph, 5, 6);
AddEdgeUndirectedMGraph(&undirectedMGraph, 0, 1, 2);
AddEdgeUndirectedMGraph(&undirectedMGraph, 0, 2, 3);
AddEdgeUndirectedMGraph(&undirectedMGraph, 1, 2, 4);
AddEdgeUndirectedMGraph(&undirectedMGraph, 2, 3, 5);
AddEdgeUndirectedMGraph(&undirectedMGraph, 3, 4, 6);
AddEdgeUndirectedMGraph(&undirectedMGraph, 4, 0, 7);
// 打印结果
printf("Directed Graph (Adjacency Matrix):\n");
PrintMGraph(directedMGraph);
printf("\nUndirected Graph (Adjacency Matrix):\n");
PrintMGraph(undirectedMGraph);
// 测试基本操作
printf("\nTest Basic Operations:\n");
printf("Adjacent(directedMGraph, 0, 1): %d\n", Adjacent(directedMGraph, 0, 1));
printf("Adjacent(directedMGraph, 0, 3): %d\n", Adjacent(directedMGraph, 0, 3));
printf("Neighbors(directedMGraph, 0): ");
Neighbors(directedMGraph, 0);
printf("Neighbors(undirectedMGraph, 0): ");
Neighbors(undirectedMGraph, 0);
InsertVertex(&directedMGraph, 5);
printf("After InsertVertex(directedMGraph, 5):\n");
PrintMGraph(directedMGraph);
DeleteVertex(&directedMGraph, 5);
printf("After DeleteVertex(directedMGraph, 5):\n");
PrintMGraph(directedMGraph);
AddEdge(&directedMGraph, 1, 4, 8);
printf("After AddEdge(directedMGraph, 1, 4):\n");
PrintMGraph(directedMGraph);
RemoveEdge(&directedMGraph, 1, 4);
printf("After RemoveEdge(directedMGraph, 1, 4):\n");
PrintMGraph(directedMGraph);
printf("FirstNeighbor(undirectedMGraph, 2): %d\n", FirstNeighbor(undirectedMGraph, 2));
printf("NextNeighbor(undirectedMGraph, 2, 3): %d\n", NextNeighbor(undirectedMGraph, 2, 3));
printf("Get_edge_value(undirectedMGraph, 2, 3): %d\n", Get_edge_value(undirectedMGraph, 2, 3));
Set_edge_value(&undirectedMGraph, 2, 3, 10);
printf("After Set_edge_value(undirectedMGraph, 2, 3, 10):\n");
PrintMGraph(undirectedMGraph);
// 测试广度优先遍历和深度优先遍历
printf("\nTest Traversal:\n");
printf("BFS directed Graph:");
BFSTraverse(directedMGraph); // 广度优先遍历有向图,起始节点为0
printf("\nDFS directed Graph:");
DFSTraverse(directedMGraph); // 深度优先遍历有向图,起始节点为0
printf("\nBFS undirected Graph:");
BFSTraverse(undirectedMGraph); // 广度优先遍历无向图,起始节点为0
printf("\nDFS undirected Graph:");
DFSTraverse(undirectedMGraph); // 深度优先遍历无向图,起始节点为0
return 0;
}
*完整代码 邻接表
#include <stdio.h>
#include <stdlib.h>
#define MaxVertexNum 100 // 最大顶点数定义为100
#define INF 9999 // 代表无穷大的定义为9999
// 边结点的定义
typedef struct ArcNode {
int adjvex; // 边指向哪个结点
int weight; // 边权值
struct ArcNode *next; // 指向下一条弧的指针
} ArcNode;
// 顶点结点的定义
typedef struct VNode {
int data; // 顶点信息
ArcNode *first; // 第一条边
} VNode, AdjList[MaxVertexNum]; // 邻接表的定义
// 图的定义
typedef struct {
AdjList vertices; // 顶点数组
int vexnum, arcnum; // 顶点数和边数
} ALGraph;
// 初始化邻接表图
void InitALGraph(ALGraph *G, int vexnum, int arcnum) {
G->vexnum = vexnum; // 初始化顶点数
G->arcnum = arcnum; // 初始化边数
for (int i = 0; i < vexnum; i++) { // 遍历顶点数组
G->vertices[i].data = i; // 顶点信息为i
G->vertices[i].first = NULL; // 第一条边为空
}
}
// 添加有向图的边
void AddEdgeDirectedALGraph(ALGraph *G, int v1, int v2, int weight) {
ArcNode *arcNode = (ArcNode *)malloc(sizeof(ArcNode)); // 分配边结点内存
arcNode->adjvex = v2; // 边指向v2
arcNode->weight = weight; // 边权值为weight
arcNode->next = G->vertices[v1].first; // 将边插入到顶点v1的边链表中
G->vertices[v1].first = arcNode;
}
// 添加无向图的边
void AddEdgeUndirectedALGraph(ALGraph *G, int v1, int v2, int weight) {
// 添加v1到v2的边
ArcNode *arcNode1 = (ArcNode *)malloc(sizeof(ArcNode)); // 分配边结点内存
arcNode1->adjvex = v2; // 边指向v2
arcNode1->weight = weight; // 边权值为weight
arcNode1->next = G->vertices[v1].first; // 将边插入到顶点v1的边链表中
G->vertices[v1].first = arcNode1;
// 添加v2到v1的边
ArcNode *arcNode2 = (ArcNode *)malloc(sizeof(ArcNode)); // 分配边结点内存
arcNode2->adjvex = v1; // 边指向v1
arcNode2->weight = weight; // 边权值为weight
arcNode2->next = G->vertices[v2].first; // 将边插入到顶点v2的边链表中
G->vertices[v2].first = arcNode2;
}
// 打印邻接表图
void PrintALGraph(ALGraph G) {
for (int i = 0; i < G.vexnum; i++) { // 遍历顶点数组
printf("%d -> ", G.vertices[i].data); // 打印顶点信息
ArcNode *p = G.vertices[i].first; // 获取顶点i的第一条边
while (p != NULL) { // 遍历顶点i的边链表
printf("(%d, %d) ", p->adjvex, p->weight); // 打印边的信息
p = p->next; // 指向下一条边
}
printf("\n");
}
}
// 判断两个顶点之间是否有边相连
int Adjacent(ALGraph G, int x, int y) {
ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
while (p != NULL) { // 遍历顶点x的边链表
if (p->adjvex == y) // 如果边的另一端是y
return 1; // 返回1,表示存在边<x, y>
p = p->next; // 指向下一条边
}
return 0; // 遍历完所有边仍未找到,返回0,表示不存在边<x, y>
}
// 打印顶点x的邻居顶点
void Neighbors(ALGraph G, int x) {
ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
printf("Neighbors of vertex %d: ", x); // 打印提示信息
while (p != NULL) { // 遍历顶点x的边链表
printf("%d ", p->adjvex); // 打印邻居顶点
p = p->next; // 指向下一条边
}
printf("\n");
}
// 插入顶点
void InsertVertex(ALGraph *G, int x) {
G->vertices[G->vexnum].data = x; // 在最后一个位置插入顶点x的信息
G->vertices[G->vexnum].first = NULL; // 边链表为空
G->vexnum++; // 顶点数加一
}
// 删除顶点
void DeleteVertex(ALGraph *G, int x) {
for (int i = 0; i < G->vexnum; i++) { // 遍历顶点数组
ArcNode *p = G->vertices[i].first; // 获取顶点i的第一条边
ArcNode *pre = NULL; // 初始化前一个边结点为NULL
while (p != NULL) { // 遍历顶点i的边链表
if (p->adjvex == x) { // 如果边的另一端是x
if (pre == NULL) // 如果是第一条边
G->vertices[i].first = p->next; // 将顶点i的第一条边指向下一条边
else
pre->next = p->next; // 将前一条边的next指针指向下一条边
free(p); // 释放删除的边结点的内存
break; // 结束循环
}
pre = p; // 更新前一个边结点
p = p->next; // 指向下一条边
}
}
// 删除顶点x
for (int i = x; i < G->vexnum - 1; i++) // 从顶点x开始,将后面的顶点信息依次往前移动
G->vertices[i] = G->vertices[i + 1]; // 将后一个顶点信息覆盖前一个顶点信息
G->vexnum--; // 顶点数减一
}
// 添加边
void AddEdge(ALGraph *G, int x, int y, int weight) {
if (!Adjacent(*G, x, y)) { // 如果两个顶点之间不存在边相连
AddEdgeUndirectedALGraph(G, x, y, weight); // 添加边<x, y>
}
}
// 删除边
void RemoveEdge(ALGraph *G, int x, int y) {
if (Adjacent(*G, x, y)) { // 如果两个顶点之间存在边相连
ArcNode *p = G->vertices[x].first; // 获取顶点x的第一条边
ArcNode *pre = NULL; // 初始化前一个边结点为NULL
while (p != NULL) { // 遍历顶点x的边链表
if (p->adjvex == y) { // 如果边的另一端是y
if (pre == NULL) // 如果是第一条边
G->vertices[x].first = p->next; // 将顶点x的第一条边指向下一条边
else
pre->next = p->next; // 将前一条边的next指针指向下一条边
free(p); // 释放删除的边结点的内存
break; // 结束循环
}
pre = p; // 更新前一个边结点
p = p->next; // 指向下一条边
}
// 如果是无向图,还需要删除另一个方向的边
if (G->vertices[y].first != NULL) { // 如果顶点y有边相连
p = G->vertices[y].first; // 获取顶点y的第一条边
pre = NULL; // 初始化前一个边结点为NULL
while (p != NULL) { // 遍历顶点y的边链表
if (p->adjvex == x) { // 如果边的另一端是x
if (pre == NULL) // 如果是第一条边
G->vertices[y].first = p->next; // 将顶点y的第一条边指向下一条边
else
pre->next = p->next; // 将前一条边的next指针指向下一条边
free(p); // 释放删除的边结点的内存
break; // 结束循环
}
pre = p; // 更新前一个边结点
p = p->next; // 指向下一条边
}
}
}
}
// 获取顶点x的第一个邻居顶点
int FirstNeighbor(ALGraph G, int x) {
if (G.vertices[x].first != NULL) // 如果顶点x有边相连
return G.vertices[x].first->adjvex; // 返回第一个邻居顶点的信息
else
return -1; // 否则返回-1,表示没有邻居顶点
}
// 获取顶点x的邻居顶点y的下一个邻居顶点
int NextNeighbor(ALGraph G, int x, int y) {
ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
while (p != NULL && p->adjvex != y) // 遍历顶点x的边链表,直到找到邻居顶点y
p = p->next; // 指向下一条边
if (p != NULL && p->next != NULL) // 如果邻居顶点y后面还有邻居顶点
return p->next->adjvex; // 返回邻居顶点y的下一个邻居顶点的信息
else
return -1; // 否则返回-1,表示没有下一个邻居顶点
}
// 获取边<x, y>的权值
int Get_edge_value(ALGraph G, int x, int y) {
ArcNode *p = G.vertices[x].first; // 获取顶点x的第一条边
while (p != NULL) { // 遍历顶点x的边链表
if (p->adjvex == y) // 如果边的另一端是y
return p->weight; // 返回边的权值
p = p->next; // 指向下一条边
}
return INF; // 遍历完所有边仍未找到,返回无穷大,表示不存在边<x, y>
}
// 设置边<x, y>的权值为v
void Set_edge_value(ALGraph *G, int x, int y, int v) {
ArcNode *p = G->vertices[x].first; // 获取顶点x的第一条边
while (p != NULL) { // 遍历顶点x的边链表
if (p->adjvex == y) { // 如果边的另一端是y
p->weight = v; // 设置边的权值为v
break; // 结束循环
}
p = p->next; // 指向下一条边
}
}
void visit(int v){
printf("%d ",v);
}
// 定义队列结点
typedef struct QueueNode {
int data;
struct QueueNode* next;
} QueueNode;
// 定义队列
typedef struct {
QueueNode *front;
QueueNode *rear;
} Queue;
// 初始化队列
void InitQueue(Queue *Q) {
Q->front = Q->rear = NULL;
}
// 入队操作
void EnQueue(Queue *Q, int data) {
QueueNode *newNode = (QueueNode *)malloc(sizeof(QueueNode));
newNode->data = data;
newNode->next = NULL;
if (Q->rear == NULL) {
Q->front = Q->rear = newNode;
} else {
Q->rear->next = newNode;
Q->rear = newNode;
}
}
// 出队操作
int DeQueue(Queue *Q) {
if (Q->front == NULL)
return -1; // 队列为空
QueueNode *temp = Q->front;
int data = temp->data;
Q->front = Q->front->next;
if (Q->front == NULL)
Q->rear = NULL;
free(temp);
return data;
}
// 判断队列是否为空
int IsQueueEmpty(Queue Q) {
return Q.front == NULL;
}
// 广度优先遍历
void BFS(ALGraph G, int v, bool visited[], Queue *Q) { // 从顶点v出发,广度优先遍历图G
visit(v); // 访问初始顶点v
visited[v] = true; // 对v做已访问标记
EnQueue(Q, v); // 顶点v入队列Q
while (!IsQueueEmpty(*Q)) {
int w;
w=DeQueue(Q); // 顶点v出队列
for (w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)) {
// 检测v所有邻接点
if (!visited[w]) { // w为v的尚未访问的邻接顶点
visit(w); // 访问w
visited[w] = true; // 标记w已被访问
EnQueue(Q, w); // 顶点w入队
}
}
}
}
// 广度优先遍历
// 对图G进行广度优先遍历
void BFSTraverse(ALGraph G) {
bool visited[MaxVertexNum] = {false};
Queue Q;
InitQueue(&Q); // 初始化辅助队列
// 从0号结点开始遍历
for (int i = 0; i < G.vexnum; ++i) {
if (!visited[i]) // 对每个连通分量调用一次BFS
BFS(G, i, visited, &Q); // vi未被访问,则从vi开始BFS
}
}
// 深度优先遍历
void DFS(ALGraph G, int v, bool visited[]) {
visit(v); // 访问顶点v
visited[v] = true; // 设已访问标记
for (int w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w)) {
if (!visited[w]) {
DFS(G, w, visited);
}
}
}
void DFSTraverse(ALGraph G) {
bool visited[MaxVertexNum] = {false};
for (int i = 0; i < G.vexnum; ++i) {
if (!visited[i]) {
DFS(G, i, visited);
}
}
}
int main() {
// 创建有向图的邻接表表示
ALGraph directedALGraph; // 定义有向图
InitALGraph(&directedALGraph, 5, 6); // 初始化有向图
AddEdgeDirectedALGraph(&directedALGraph, 0, 1, 1); // 添加边
AddEdgeDirectedALGraph(&directedALGraph, 0, 2, 2); // 添加边
AddEdgeDirectedALGraph(&directedALGraph, 1, 2, 3); // 添加边
AddEdgeDirectedALGraph(&directedALGraph, 2, 3, 4); // 添加边
AddEdgeDirectedALGraph(&directedALGraph, 3, 4, 5); // 添加边
AddEdgeDirectedALGraph(&directedALGraph, 4, 0, 6); // 添加边
// 创建无向图的邻接表表示
ALGraph undirectedALGraph; // 定义无向图
InitALGraph(&undirectedALGraph, 5, 6); // 初始化无向图
AddEdgeUndirectedALGraph(&undirectedALGraph, 0, 1, 1); // 添加边
AddEdgeUndirectedALGraph(&undirectedALGraph, 0, 2, 2); // 添加边
AddEdgeUndirectedALGraph(&undirectedALGraph, 1, 2, 3); // 添加边
AddEdgeUndirectedALGraph(&undirectedALGraph, 2, 3, 4); // 添加边
AddEdgeUndirectedALGraph(&undirectedALGraph, 3, 4, 5); // 添加边
AddEdgeUndirectedALGraph(&undirectedALGraph, 4, 0, 6); // 添加边
printf("\nDirected Graph (Adjacency List):\n");
PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
printf("\nUndirected Graph (Adjacency List):\n");
PrintALGraph(undirectedALGraph); // 打印无向图的邻接表表示
// 测试基本操作
printf("\nTest Basic Operations:\n");
printf("Adjacent(directedALGraph, 0, 1): %d\n", Adjacent(directedALGraph, 0, 1)); // 测试两个顶点之间是否有边相连
printf("Adjacent(directedALGraph, 0, 3): %d\n", Adjacent(directedALGraph, 0, 3)); // 测试两个顶点之间是否有边相连
printf("Adjacent(undirectedALGraph, 0, 1): %d\n", Adjacent(undirectedALGraph, 0, 1)); // 测试两个顶点之间是否有边相连
printf("Neighbors(directedALGraph, 0): ");
Neighbors(directedALGraph, 0); // 打印顶点0的邻居顶点
printf("Neighbors(undirectedALGraph, 0): ");
Neighbors(undirectedALGraph, 0); // 打印顶点0的邻居顶点
InsertVertex(&directedALGraph, 5); // 插入顶点5
printf("After InsertVertex(directedALGraph, 5):\n");
PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
DeleteVertex(&directedALGraph, 5); // 删除顶点5
printf("After DeleteVertex(directedALGraph, 5):\n");
PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
AddEdge(&directedALGraph, 1, 4, 7); // 添加边
printf("After AddEdge(directedALGraph, 1, 4, 7):\n");
PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
RemoveEdge(&directedALGraph, 1, 4); // 删除边
printf("After RemoveEdge(directedALGraph, 1, 4):\n");
PrintALGraph(directedALGraph); // 打印有向图的邻接表表示
printf("FirstNeighbor(undirectedALGraph, 2): %d\n", FirstNeighbor(undirectedALGraph, 2)); // 获取顶点2的第一个邻居顶点
printf("NextNeighbor(undirectedALGraph, 2, 3): %d\n", NextNeighbor(undirectedALGraph, 2, 3)); // 获取顶点2的邻居顶点3的下一个邻居顶点
printf("Get_edge_value(undirectedALGraph, 2, 3): %d\n", Get_edge_value(undirectedALGraph, 2, 3)); // 获取边
Set_edge_value(&undirectedALGraph, 2, 3, 10);
printf("After Set_edge_value(undirectedALGraph, 2, 3, 10):\n");
PrintALGraph(undirectedALGraph);
// 测试广度优先遍历和深度优先遍历
printf("\nTest Traversal:\n");
printf("BFS directed Graph:");
BFSTraverse(directedALGraph); // 广度优先遍历有向图,起始节点为0
printf("\nDFS directed Graph:");
DFSTraverse(directedALGraph); // 深度优先遍历有向图,起始节点为0
printf("\nBFS undirected Graph:");
BFSTraverse(undirectedALGraph); // 广度优先遍历无向图,起始节点为0
printf("\nDFS undirected Graph:");
DFSTraverse(undirectedALGraph); // 深度优先遍历无向图,起始节点为0
return 0;
}