数据结构 图(Graph)
1. 图介绍
-
在数据结构中,图 Graph是一种非常重要的数据结构,用于表示不同的对象(主要是节点与边)之间的关系。
-
图由节点V(顶点)(Vertex)和边E(Edge)组成,即有Graph={∑V+∑E},节点表示图中的元素,而边表示节点之间的关系。图可以用于建模各种实际问题,如社交网络中的用户关系、计算机网络中的路由、地图中的道路网络等。
-
图可以分为有向图DAG和无向图G两种类型。在有向图中,边有方向,表示从一个节点到另一个节点的单向关系;而在无向图中,边没有方向,表示节点之间的双向关系。
-
图还可以根据边的权重分为带权图和无权图。带权图中,每条边都有一个相关联的权重或成本W(Weight),此时Graph={∑V+∑E+∑W},而在无权图中,所有边的权重都是相同的(1或者常数),或者没有权重。
-
图的常见操作包括添加节点、添加边、删除节点、删除边、查找节点、查找边等。图的遍历算法包括深度优先搜索(DFS)和广度优先搜索(BFS),用于遍历图中的所有节点。
-
入度和出度,入度表示有多少条边指向特定的节点,而出度表示从特定的节点出发一共有多少边指向其他节点。
2. 图分类
2.1 按有无方向划分
2.1.1 有向图
- 有向图无环(DAG)的性质:
- DAG是一个有向图,其中边有方向,表示从一个节点到另一个节点的单向关系。
- DAG 不包含任何环路,即不存在从某个节点出发经过若干条边回到该节点的路径。因此,DAG 的结构是一个有向无环的图。
- DAG 可以用来表示一些具有前后关系的任务或事件,如任务调度、依赖关系等。例如,编译过程中的源文件依赖关系就可以用 DAG 来表示。
- 由于没有环路,DAG 中不存在循环依赖的问题,因此可以进行拓扑排序等操作。
2.1.2 无向图
- 无向图(G)的性质:
- 无向图中的边没有方向,表示节点之间的双向关系,即任意两个节点之间都可以互相到达。
- 无向图可以有环路,即存在一条路径可以从某个节点出发经过若干条边回到该节点。
- 无向图通常用来表示不考虑方向的关系,如社交网络中的好友关系、交通网络中的道路连接等。
- 无向图中的边是对称的,即如果节点 A 与节点 B 之间有一条边,那么节点 B 与节点 A 之间也有一条边。
2.1.3 图的存储方式
图主要以邻接表和邻接矩阵作为存储媒介进行存储。
2.1.3.1 邻接表
-
邻接表是一种基于链表的数据结构,用于表示图的结构。
对于每个节点,邻接表存储其相邻节点的列表。通常是通过数组和链表的组合来实现,其中数组的索引表示节点,而每个数组元素是一个链表,存储与该节点相邻的节点。
邻接表适用于稀疏图,即节点数相对边数较少的图,因为它只存储实际存在的边,节省了空间。
2.1.3.2 邻接矩阵
- 邻接矩阵是一个二维数组,用于表示图的结构。
对于具有 n 个节点的图,邻接矩阵是一个 n × n 的矩阵,其中行和列分别表示节点,矩阵的元素表示节点之间是否有边相连。
如果图是有向图,邻接矩阵中的元素可以是 0 或 1,表示没有边或有边;如果图是带权图,邻接矩阵中的元素可以是实际的权重值W。
邻接矩阵适用于稠密图,即节点数和边数相对较多的图,因为它提供了 O(1) 时间复杂度的边查找操作,但是占用了更多的空间。
2.2 按权重划分
2.2.1 带权图
-
带权图是指图中的边携带有权重或者相关的数值信息。
-
在带权图中,每条边都与一个权重值相关联,表示边的强度、长度、成本等。
-
带权图常用于建模一些实际问题,如最短路径问题、列车调度问题、哥尼斯堡的“七桥问题”等。
-
带权图也可以是无向图或有向图,无向带权图表示节点之间的双向关系并携带权重,有向带权图表示单向关系并携带权重。
2.2.2 无权图
-
不带权图是指图中的边没有权重或者没有相关的数值信息。
-
在不带权图中,边只表示节点之间的连接关系,而不包含额外的信息。
-
无向图和有向图都可以是不带权图,其中无向图的边表示节点之间的双向关系,而有向图的边表示单向关系。
3. 图常见操作
3.1 添加节点(顶点)
用于向图中添加新的节点。
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100 // 假设最大节点数量为100
// 图的结构体定义
typedef struct {
int numVertices; // 节点数量
int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;
// 初始化图
void initializeGraph(Graph *graph) {
int i, j;
graph->numVertices = 0;
for (i = 0; i < MAX_VERTICES; i++) {
for (j = 0; j < MAX_VERTICES; j++) {
graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
}
}
}
// 向图中添加新节点
void addVertex(Graph *graph) {
if (graph->numVertices < MAX_VERTICES) {
// 新节点的索引即为当前节点数量
graph->numVertices++;
} else {
printf("图中节点数量已达到最大值,无法添加新节点。\n");
}
}
int main() {
Graph graph;
initializeGraph(&graph);
// 添加三个节点
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
printf("图中当前节点数量:%d\n", graph.numVertices);
return 0;
}
3.2 删除节点
从图中删除指定的节点,以及与该节点相关联的边。
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100 // 假设最大节点数量为100
// 图的结构体定义
typedef struct {
int numVertices; // 节点数量
int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;
// 初始化图
void initializeGraph(Graph *graph) {
int i, j;
graph->numVertices = 0;
for (i = 0; i < MAX_VERTICES; i++) {
for (j = 0; j < MAX_VERTICES; j++) {
graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
}
}
}
// 向图中添加新节点
void addVertex(Graph *graph) {
if (graph->numVertices < MAX_VERTICES) {
// 新节点的索引即为当前节点数量
graph->numVertices++;
} else {
printf("图中节点数量已达到最大值,无法添加新节点。\n");
}
}
// 从图中删除节点及其相关联的边
void removeVertex(Graph *graph, int vertex) {
if (vertex >= 0 && vertex < graph->numVertices) {
int i, j;
// 从邻接矩阵中删除相关联的边
for (i = 0; i < graph->numVertices; i++) {
graph->adjMatrix[vertex][i] = 0; // 删除与该节点相关联的出边
graph->adjMatrix[i][vertex] = 0; // 删除与该节点相关联的入边
}
// 将该节点从图中删除
for (i = vertex; i < graph->numVertices - 1; i++) {
for (j = 0; j < graph->numVertices; j++) {
graph->adjMatrix[j][i] = graph->adjMatrix[j][i + 1]; // 将后面的节点向前移动
}
}
for (i = vertex; i < graph->numVertices - 1; i++) {
for (j = 0; j < graph->numVertices; j++) {
graph->adjMatrix[i][j] = graph->adjMatrix[i + 1][j]; // 将后面的节点向前移动
}
}
graph->numVertices--; // 更新节点数量
} else {
printf("要删除的节点不存在。\n");
}
}
int main() {
Graph graph;
initializeGraph(&graph);
// 添加四个节点
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
// 添加一些边
graph.adjMatrix[0][1] = 1; // 添加从节点0到节点1的边
graph.adjMatrix[1][2] = 1; // 添加从节点1到节点2的边
graph.adjMatrix[2][3] = 1; // 添加从节点2到节点3的边
printf("删除前,图中当前节点数量:%d\n", graph.numVertices);
// 删除节点2及其相关联的边
removeVertex(&graph, 2);
printf("删除后,图中当前节点数量:%d\n", graph.numVertices);
return 0;
}
3.3 添加边
在图中添加一条边,连接两个节点。
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100 // 假设最大节点数量为100
// 图的结构体定义
typedef struct {
int numVertices; // 节点数量
int numEdges; // 边的数量
int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;
// 初始化图
void initializeGraph(Graph *graph) {
int i, j;
graph->numVertices = 0;
graph->numEdges = 0;
for (i = 0; i < MAX_VERTICES; i++) {
for (j = 0; j < MAX_VERTICES; j++) {
graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
}
}
}
// 向图中添加新节点
void addVertex(Graph *graph) {
if (graph->numVertices < MAX_VERTICES) {
// 新节点的索引即为当前节点数量
graph->numVertices++;
} else {
printf("图中节点数量已达到最大值,无法添加新节点。\n");
}
}
// 在图中添加一条边,连接两个节点
void addEdge(Graph *graph, int source, int destination) {
if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
// 添加边将邻接矩阵中对应位置设为1
graph->adjMatrix[source][destination] = 1;
// 如果是无向图,还需添加反向边
graph->adjMatrix[destination][source] = 1;
// 更新边的数量
graph->numEdges++;
} else {
printf("节点索引无效,无法添加边。\n");
}
}
// 从图中删除一条边
void removeEdge(Graph *graph, int source, int destination) {
if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
// 删除边将邻接矩阵中对应位置设为0
graph->adjMatrix[source][destination] = 0;
// 如果是无向图,还需删除反向边
graph->adjMatrix[destination][source] = 0;
// 更新边的数量
graph->numEdges--;
} else {
printf("节点索引无效,无法删除边。\n");
}
}
int main() {
Graph graph;
initializeGraph(&graph);
// 添加四个节点
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
printf("添加边前,图中当前边的数量:%d\n", graph.numEdges);
// 添加边连接节点0和节点1
addEdge(&graph, 0, 1);
addEdge(&graph, 1, 2);
printf("添加边后,图中当前边的数量:%d\n", graph.numEdges);
return 0;
}
3.4 删除边
从图中删除指定的边。
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100 // 假设最大节点数量为100
// 图的结构体定义
typedef struct {
int numVertices; // 节点数量
int numEdges; // 边的数量
int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;
// 初始化图
void initializeGraph(Graph *graph) {
int i, j;
graph->numVertices = 0;
graph->numEdges = 0;
for (i = 0; i < MAX_VERTICES; i++) {
for (j = 0; j < MAX_VERTICES; j++) {
graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
}
}
}
// 向图中添加新节点
void addVertex(Graph *graph) {
if (graph->numVertices < MAX_VERTICES) {
// 新节点的索引即为当前节点数量
graph->numVertices++;
} else {
printf("图中节点数量已达到最大值,无法添加新节点。\n");
}
}
// 在图中添加一条边,连接两个节点
void addEdge(Graph *graph, int source, int destination) {
if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
// 添加边将邻接矩阵中对应位置设为1
graph->adjMatrix[source][destination] = 1;
// 如果是无向图,还需添加反向边
graph->adjMatrix[destination][source] = 1;
// 更新边的数量
graph->numEdges++;
} else {
printf("节点索引无效,无法添加边。\n");
}
}
// 从图中删除一条边
void removeEdge(Graph *graph, int source, int destination) {
if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
// 删除边将邻接矩阵中对应位置设为0
graph->adjMatrix[source][destination] = 0;
// 如果是无向图,还需删除反向边
graph->adjMatrix[destination][source] = 0;
// 更新边的数量
graph->numEdges--;
} else {
printf("节点索引无效,无法删除边。\n");
}
}
int main() {
Graph graph;
initializeGraph(&graph);
// 添加四个节点
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
// 添加边连接节点0和节点1
addEdge(&graph, 0, 1);
addEdge(&graph, 1, 2);
printf("图中当前边的数量:%d\n", graph.numEdges);
// 删除连接节点0和节点1的边
removeEdge(&graph, 0, 1);
printf("删除边后,图中当前边的数量:%d\n", graph.numEdges);
return 0;
}
3.5 查找节点
在图中查找指定的节点。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_VERTICES 100 // 假设最大节点数量为100
// 图的结构体定义
typedef struct {
int numVertices; // 节点数量
int numEdges; // 边的数量
int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;
// 初始化图
void initializeGraph(Graph *graph) {
int i, j;
graph->numVertices = 0;
graph->numEdges = 0;
for (i = 0; i < MAX_VERTICES; i++) {
for (j = 0; j < MAX_VERTICES; j++) {
graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
}
}
}
// 向图中添加新节点
void addVertex(Graph *graph) {
if (graph->numVertices < MAX_VERTICES) {
// 新节点的索引即为当前节点数量
graph->numVertices++;
} else {
printf("图中节点数量已达到最大值,无法添加新节点。\n");
}
}
// 在图中添加一条边,连接两个节点
void addEdge(Graph *graph, int source, int destination) {
if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
// 添加边将邻接矩阵中对应位置设为1
graph->adjMatrix[source][destination] = 1;
// 如果是无向图,还需添加反向边
graph->adjMatrix[destination][source] = 1;
// 更新边的数量
graph->numEdges++;
} else {
printf("节点索引无效,无法添加边。\n");
}
}
// 在图中查找指定的节点
bool findVertex(Graph *graph, int vertex) {
if (vertex >= 0 && vertex < graph->numVertices) {
printf("节点 %d 存在于图中。\n", vertex);
return true;
} else {
printf("节点 %d 不存在于图中。\n", vertex);
return false;
}
}
// 从图中删除一条边
void removeEdge(Graph *graph, int source, int destination) {
if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
// 删除边将邻接矩阵中对应位置设为0
graph->adjMatrix[source][destination] = 0;
// 如果是无向图,还需删除反向边
graph->adjMatrix[destination][source] = 0;
// 更新边的数量
graph->numEdges--;
} else {
printf("节点索引无效,无法删除边。\n");
}
}
int main() {
Graph graph;
initializeGraph(&graph);
// 添加四个节点
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
// 添加边连接节点0和节点1
addEdge(&graph, 0, 1);
addEdge(&graph, 1, 2);
int targetVertex = 3;
findVertex(&graph, targetVertex);
return 0;
}
3.6 查找边
在图中查找两个节点之间是否存在边。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_VERTICES 100 // 假设最大节点数量为100
// 图的结构体定义
typedef struct {
int numVertices; // 节点数量
int numEdges; // 边的数量
int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;
// 初始化图
void initializeGraph(Graph *graph) {
int i, j;
graph->numVertices = 0;
graph->numEdges = 0;
for (i = 0; i < MAX_VERTICES; i++) {
for (j = 0; j < MAX_VERTICES; j++) {
graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
}
}
}
// 向图中添加新节点
void addVertex(Graph *graph) {
if (graph->numVertices < MAX_VERTICES) {
// 新节点的索引即为当前节点数量
graph->numVertices++;
} else {
printf("图中节点数量已达到最大值,无法添加新节点。\n");
}
}
// 在图中添加一条边,连接两个节点
void addEdge(Graph *graph, int source, int destination) {
if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
// 添加边将邻接矩阵中对应位置设为1
graph->adjMatrix[source][destination] = 1;
// 如果是无向图,还需添加反向边
graph->adjMatrix[destination][source] = 1;
// 更新边的数量
graph->numEdges++;
} else {
printf("节点索引无效,无法添加边。\n");
}
}
// 在图中查找指定的节点
bool findVertex(Graph *graph, int vertex) {
if (vertex >= 0 && vertex < graph->numVertices) {
printf("节点 %d 存在于图中。\n", vertex);
return true;
} else {
printf("节点 %d 不存在于图中。\n", vertex);
return false;
}
}
// 在图中检查两个节点之间是否存在边
bool hasEdge(Graph *graph, int source, int destination) {
if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
if (graph->adjMatrix[source][destination] == 1) {
printf("节点 %d 和节点 %d 之间存在边。\n", source, destination);
return true;
} else {
printf("节点 %d 和节点 %d 之间不存在边。\n", source, destination);
return false;
}
} else {
printf("节点索引无效,无法查找边。\n");
return false;
}
}
// 从图中删除一条边
void removeEdge(Graph *graph, int source, int destination) {
if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
// 删除边将邻接矩阵中对应位置设为0
graph->adjMatrix[source][destination] = 0;
// 如果是无向图,还需删除反向边
graph->adjMatrix[destination][source] = 0;
// 更新边的数量
graph->numEdges--;
} else {
printf("节点索引无效,无法删除边。\n");
}
}
int main() {
Graph graph;
initializeGraph(&graph);
// 添加四个节点
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
// 添加边连接节点0和节点1
addEdge(&graph, 0, 1);
addEdge(&graph, 1, 2);
// 检查两个节点之间是否存在边
int node1 = 0;
int node2 = 1;
hasEdge(&graph, node1, node2);
return 0;
}
3.7 获取节点的邻居(相邻节点)
获取与指定节点直接相连的节点集合。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_VERTICES 100 // 假设最大节点数量为100
// 图的结构体定义
typedef struct {
int numVertices; // 节点数量
int numEdges; // 边的数量
int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;
// 初始化图
void initializeGraph(Graph *graph) {
int i, j;
graph->numVertices = 0;
graph->numEdges = 0;
for (i = 0; i < MAX_VERTICES; i++) {
for (j = 0; j < MAX_VERTICES; j++) {
graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
}
}
}
// 向图中添加新节点
void addVertex(Graph *graph) {
if (graph->numVertices < MAX_VERTICES) {
// 新节点的索引即为当前节点数量
graph->numVertices++;
} else {
printf("图中节点数量已达到最大值,无法添加新节点。\n");
}
}
// 在图中添加一条边,连接两个节点
void addEdge(Graph *graph, int source, int destination) {
if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
// 添加边将邻接矩阵中对应位置设为1
graph->adjMatrix[source][destination] = 1;
// 如果是无向图,还需添加反向边
graph->adjMatrix[destination][source] = 1;
// 更新边的数量
graph->numEdges++;
} else {
printf("节点索引无效,无法添加边。\n");
}
}
// 在图中查找指定的节点
bool findVertex(Graph *graph, int vertex) {
if (vertex >= 0 && vertex < graph->numVertices) {
printf("节点 %d 存在于图中。\n", vertex);
return true;
} else {
printf("节点 %d 不存在于图中。\n", vertex);
return false;
}
}
// 在图中检查两个节点之间是否存在边
bool hasEdge(Graph *graph, int source, int destination) {
if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
if (graph->adjMatrix[source][destination] == 1) {
printf("节点 %d 和节点 %d 之间存在边。\n", source, destination);
return true;
} else {
printf("节点 %d 和节点 %d 之间不存在边。\n", source, destination);
return false;
}
} else {
printf("节点索引无效,无法查找边。\n");
return false;
}
}
// 获取与指定节点直接相连的节点集合
void getAdjacentVertices(Graph *graph, int vertex, int adjacentVertices[], int *numAdjacent) {
if (vertex >= 0 && vertex < graph->numVertices) {
*numAdjacent = 0;
for (int i = 0; i < graph->numVertices; i++) {
if (graph->adjMatrix[vertex][i] == 1) {
adjacentVertices[*numAdjacent] = i;
(*numAdjacent)++;
}
}
} else {
printf("节点索引无效,无法获取相邻节点。\n");
}
}
// 从图中删除一条边
void removeEdge(Graph *graph, int source, int destination) {
if (source >= 0 && source < graph->numVertices && destination >= 0 && destination < graph->numVertices) {
// 删除边将邻接矩阵中对应位置设为0
graph->adjMatrix[source][destination] = 0;
// 如果是无向图,还需删除反向边
graph->adjMatrix[destination][source] = 0;
// 更新边的数量
graph->numEdges--;
} else {
printf("节点索引无效,无法删除边。\n");
}
}
int main() {
Graph graph;
initializeGraph(&graph);
// 添加四个节点
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
addVertex(&graph);
// 添加边连接节点0和节点1
addEdge(&graph, 0, 1);
addEdge(&graph, 1, 2);
// 获取与指定节点直接相连的节点集合
int node = 1;
int adjacentVertices[MAX_VERTICES];
int numAdjacent;
getAdjacentVertices(&graph, node, adjacentVertices, &numAdjacent);
printf("节点 %d 的相邻节点有:", node);
for (int i = 0; i < numAdjacent; i++) {
printf("%d ", adjacentVertices[i]);
}
printf("\n");
return 0;
}
3.8 计算节点的度
统计与节点相连的边的数量,即节点的度。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_VERTICES 100 // 假设最大节点数量为100
// 图的结构体定义
typedef struct {
int numVertices; // 节点数量
int numEdges; // 边的数量
int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;
// 初始化图
void initializeGraph(Graph *graph) {
int i, j;
graph->numVertices = 0;
graph->numEdges = 0;
for (i = 0; i < MAX_VERTICES; i++) {
for (j = 0; j < MAX_VERTICES; j++) {
graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
}
}
}
// 统计与节点相连的边的数量,即节点的度
int getDegree(Graph *graph, int vertex) {
if (vertex >= 0 && vertex < graph->numVertices) {
int degree = 0;
for (int i = 0; i < graph->numVertices; i++) {
if (graph->adjMatrix[vertex][i] == 1) {
degree++;
}
}
return degree;
} else {
printf("节点索引无效,无法获取节点度。\n");
return -1;
}
}
int main() {
Graph graph;
initializeGraph(&graph);
// 添加四个节点
graph.numVertices = 4;
// 添加边连接节点0和节点1
graph.adjMatrix[0][1] = 1;
graph.adjMatrix[1][0] = 1;
// 统计节点的度并展示
int node = 0;
int degree = getDegree(&graph, node);
printf("节点 %d 的度为 %d\n", node, degree);
return 0;
}
3.9 遍历图
访问图中所有的节点和边,以便对其进行操作或分析。
常见的图遍历算法包括深度优先搜索(DFS)和广度优先搜索(BFS)。
DFS和BFS的遍历结果可能会不一样。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_VERTICES 100 // 假设最大节点数量为100
// 图的结构体定义
typedef struct {
int numVertices; // 节点数量
int numEdges; // 边的数量
int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;
// 初始化图
void initializeGraph(Graph *graph) {
int i, j;
graph->numVertices = 0;
graph->numEdges = 0;
for (i = 0; i < MAX_VERTICES; i++) {
for (j = 0; j < MAX_VERTICES; j++) {
graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
}
}
}
// 深度优先搜索
void dfs(Graph *graph, int vertex, bool visited[]) {
visited[vertex] = true;
printf("访问节点 %d\n", vertex);
for (int i = 0; i < graph->numVertices; i++) {
if (graph->adjMatrix[vertex][i] == 1 && !visited[i]) {
dfs(graph, i, visited);
}
}
}
// 广度优先搜索
void bfs(Graph *graph, int start) {
bool visited[MAX_VERTICES] = {false};
int queue[MAX_VERTICES];
int front = 0, rear = 0;
visited[start] = true;
printf("访问节点 %d\n", start);
queue[rear++] = start;
while (front < rear) {
int current = queue[front++];
for (int i = 0; i < graph->numVertices; i++) {
if (graph->adjMatrix[current][i] == 1 && !visited[i]) {
visited[i] = true;
printf("访问节点 %d\n", i);
queue[rear++] = i;
}
}
}
}
int main() {
Graph graph;
initializeGraph(&graph);
// 添加四个节点
graph.numVertices = 4;
// 添加边连接节点0和节点1,节点1和节点2
graph.adjMatrix[0][1] = 1;
graph.adjMatrix[1][0] = 1;
graph.adjMatrix[1][2] = 1;
graph.adjMatrix[2][1] = 1;
printf("深度优先搜索结果:\n");
bool visitedDFS[MAX_VERTICES] = {false};
dfs(&graph, 0, visitedDFS);
printf("\n广度优先搜索结果:\n");
bfs(&graph, 0);
return 0;
}
3.10 检测图的连通性
判断图中是否存在路径连接任意两个节点,以及图中的连通分量数量。
在图论中,一个连通分量是指图中的一个最大子图,其中任意两个顶点都可以通过边相连。换句话说,一个连通分量是一个子图,其中从任意一个顶点出发都可以到达图中的任意其他顶点,而且这个子图不能再扩充,否则就不再是一个连通分量了。
在无向图中,每个连通分量都是一个连通的子图,而且没有两个连通分量有交集。一个无向图可能包含一个或多个连通分量。
在有向图中,连通分量被称为强连通分量(Strongly Connected Component,SCC),它是指一个有向图中的极大子图,其中任意两个顶点都可以互相到达。
可以用DFS 也可以用 BFS进行连通分量数目的统计。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_VERTICES 100 // 假设最大节点数量为100
// 图的结构体定义
typedef struct {
int numVertices; // 节点数量
int numEdges; // 边的数量
int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;
// 初始化图
void initializeGraph(Graph *graph) {
int i, j;
graph->numVertices = 0;
graph->numEdges = 0;
for (i = 0; i < MAX_VERTICES; i++) {
for (j = 0; j < MAX_VERTICES; j++) {
graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
}
}
}
// 深度优先搜索,用于判断连通分量数量
void dfs(Graph *graph, int vertex, bool visited[]) {
visited[vertex] = true;
for (int i = 0; i < graph->numVertices; i++) {
if (graph->adjMatrix[vertex][i] == 1 && !visited[i]) {
dfs(graph, i, visited);
}
}
}
// 获取图中的连通分量数量
int getConnectedComponents(Graph *graph) {
bool visited[MAX_VERTICES] = {false};
int numComponents = 0;
for (int i = 0; i < graph->numVertices; i++) {
if (!visited[i]) {
dfs(graph, i, visited);
numComponents++;
}
}
return numComponents;
}
int main() {
Graph graph;
initializeGraph(&graph);
// 添加四个节点
graph.numVertices = 4;
// 添加边连接节点0和节点1,节点1和节点2
graph.adjMatrix[0][1] = 1;
graph.adjMatrix[1][0] = 1;
graph.adjMatrix[1][2] = 1;
graph.adjMatrix[2][1] = 1;
int numComponents = getConnectedComponents(&graph);
printf("图中的连通分量数量:%d\n", numComponents);
return 0;
}
3.11 计算最短路径
计算两个节点之间的最短路径,以及最短路径的长度。
可以解决实际生活中真实存在的问题。Dijkstra (迪杰斯特拉)算法主要用于计算最短路径。
Dijkstra (迪杰斯特拉)算法的基本思想是通过不断地“释放”节点来逐步确定最短路径的长度。具体来说,它维护一个距离数组,记录了从源节点到每个节点的最短距离。初始时,源节点的距离为0,其他节点的距离为无穷大INF。然后,它通过选择未被释放的节点中距离最小的节点来进行释放操作,即更新与该节点相邻的节点的距离。这样,每次选择距离最小的节点来释放,直到所有节点都被释放,最终得到了从源节点到其他所有节点的最短路径长度。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#define MAX_VERTICES 100 // 假设最大节点数量为100
#define INF INT_MAX // 无穷大值,表示不可达
// 图的结构体定义
typedef struct {
int numVertices; // 节点数量
int numEdges; // 边的数量
int adjMatrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵表示图的连接关系
} Graph;
// 初始化图
void initializeGraph(Graph *graph) {
int i, j;
graph->numVertices = 0;
graph->numEdges = 0;
for (i = 0; i < MAX_VERTICES; i++) {
for (j = 0; j < MAX_VERTICES; j++) {
graph->adjMatrix[i][j] = 0; // 初始化邻接矩阵,表示节点间没有边相连
}
}
}
// 计算两个节点之间的最短路径长度
int shortestPathLength(Graph *graph, int source, int destination) {
int distance[MAX_VERTICES]; // 存储从起始节点到每个节点的最短距离
bool visited[MAX_VERTICES] = {false}; // 记录节点是否被访问过
int i, j;
// 初始化距离数组
for (i = 0; i < graph->numVertices; i++) {
distance[i] = INF;
}
// 起始节点到自身的距离为0
distance[source] = 0;
// 寻找最短路径
for (i = 0; i < graph->numVertices - 1; i++) {
int minDistance = INF;
int minIndex = -1;
// 选择当前未访问节点中距离起始节点最近的节点
for (j = 0; j < graph->numVertices; j++) {
if (!visited[j] && distance[j] < minDistance) {
minDistance = distance[j];
minIndex = j;
}
}
// 将选定的节点标记为已访问
visited[minIndex] = true;
// 更新与选定节点相连的节点的距离
for (j = 0; j < graph->numVertices; j++) {
if (!visited[j] && graph->adjMatrix[minIndex][j] && distance[minIndex] + graph->adjMatrix[minIndex][j] < distance[j]) {
distance[j] = distance[minIndex] + graph->adjMatrix[minIndex][j];
}
}
}
return distance[destination];
}
int main() {
Graph graph;
initializeGraph(&graph);
// 添加四个节点
graph.numVertices = 4;
// 添加边连接节点0和节点1,节点1和节点2
graph.adjMatrix[0][1] = 1;
graph.adjMatrix[1][0] = 1;
graph.adjMatrix[1][2] = 1;
graph.adjMatrix[2][1] = 1;
// 计算两个节点之间的最短路径长度并展示
int source = 0;
int destination = 2;
int shortestLength = shortestPathLength(&graph, source, destination);
if (shortestLength == INF) {
printf("节点 %d 到节点 %d 之间不存在路径。\n", source, destination);
} else {
printf("节点 %d 到节点 %d 之间的最短路径长度为 %d。\n", source, destination, shortestLength);
}
return 0;
}
4. 特殊图
特殊图主要有以下类别:
- 完全图(Complete Graph):在完全图中,每一对不同的节点都有一条边连接。一个完全图有 n(n−1)/2条边,其中 n 是节点的数量。
- 空图(Null Graph):空图是一个没有任何边的图,只包含节点。
- 有环图(Cycle Graph):环图是一个简单环上的图,即所有节点连接成一个环,每个节点与其相邻的两个节点相连。
- 树(Tree):树是一种无环且连通的无向图,其中任意两个节点之间有且仅有一条简单路径。树通常用于表示层次结构或是搜索树等数据结构。