基础数据结构【c语言版】之 “图” 详细讲述

1. 图的定义和术语

1.1 图的定义

**图(Graph)**是由顶点(Vertex)和边(Edge)组成的一个集合,可以表示顶点之间的关系。通常,图可以表示为 G=(V,E)G = (V, E)G=(V,E),其中:

  • VVV 是顶点集合,表示图中的所有顶点。
  • EEE 是边集合,表示图中顶点之间的连接关系。

图的类型:::

根据边的不同特性,图可以分为以下几种常见类型:

  1. 无向图(Undirected Graph):边没有方向,连接两个顶点的边可以双向访问。例如,表示朋友关系的社交网络图通常为无向图。

  2. 有向图(Directed Graph):边具有方向,从一个顶点指向另一个顶点。通常用箭头表示边的方向,例如,表示课程的先修关系图。

  3. 带权图(Weighted Graph):每条边上都有一个权重(Weight),表示从一个顶点到另一个顶点的“代价”,如距离、时间或费用。例如,道路图中可以用权重表示每条道路的长度。


1.2 图的术语

在学习图结构时,我们需要掌握一些常用的术语,以便更好地理解图的概念。

  1. 顶点(Vertex):图中的基本单位,每个顶点用来表示一个对象,通常记为 VVV 或者用 V1,V2,…,VnV_1, V_2, \dots, V_nV1​,V2​,…,Vn​ 来表示多个顶点。

  2. 边(Edge):顶点与顶点之间的连接,用来表示对象之间的关系,通常记为 EEE。在无向图中,边用无序对 (Vi,Vj)(V_i, V_j)(Vi​,Vj​) 表示,而在有向图中,边用有序对 (Vi,Vj)(V_i, V_j)(Vi​,Vj​) 表示,其中边的方向从 ViV_iVi​ 指向 VjV_jVj​。

  3. 度(Degree)

    • 无向图的度:在无向图中,顶点的度是连接该顶点的边的数量。例如,一个度为3的顶点表示有3条边连接到该顶点。
    • 有向图的入度和出度:在有向图中,入度(In-degree)表示指向该顶点的边数,出度(Out-degree)表示从该顶点出发的边数。
  4. 路径(Path):从一个顶点到另一个顶点所经过的顶点序列。路径的长度是路径上边的数目。

    • 简单路径:不包含重复顶点的路径。
    • 回路(Cycle):起点和终点相同的路径称为回路。
  5. 连通图(Connected Graph):在无向图中,如果任意两个顶点之间都存在路径,则称该图为连通图。否则称为非连通图。

  6. 强连通图(Strongly Connected Graph):在有向图中,如果任意两个顶点之间都存在路径(即双向可达),则称该图为强连通图。

  7. 稀疏图(Sparse Graph)和稠密图(Dense Graph)

    • 稀疏图:边数远小于顶点数的平方,常常用邻接表表示。
    • 稠密图:边数接近顶点数的平方,常常用邻接矩阵表示。

1.3 C语言中的图表示

在C语言中,我们通常使用邻接矩阵或邻接表来存储图。以下是用邻接矩阵表示无向图的简单示例代码:

#include <stdio.h>#define MAX_VERTICES 5  // 假设顶点数量最大为5void initializeGraph(int graph[MAX_VERTICES][MAX_VERTICES]) {for (int i = 0; i < MAX_VERTICES; i++) {for (int j = 0; j < MAX_VERTICES; j++) {graph[i][j] = 0;  // 初始化图中所有边为0(即无连接)}}
}void addEdge(int graph[MAX_VERTICES][MAX_VERTICES], int u, int v) {graph[u][v] = 1;graph[v][u] = 1;  // 无向图,边(u, v) 和 (v, u) 都需设置为1
}void printGraph(int graph[MAX_VERTICES][MAX_VERTICES]) {for (int i = 0; i < MAX_VERTICES; i++) {for (int j = 0; j < MAX_VERTICES; j++) {printf("%d ", graph[i][j]);}printf("\n");}
}int main() {int graph[MAX_VERTICES][MAX_VERTICES];initializeGraph(graph);// 添加边addEdge(graph, 0, 1);addEdge(graph, 0, 4);addEdge(graph, 1, 2);addEdge(graph, 1, 3);addEdge(graph, 1, 4);addEdge(graph, 2, 3);addEdge(graph, 3, 4);printf("图的邻接矩阵表示:\n");printGraph(graph);return 0;
}
  1. initializeGraph:初始化邻接矩阵,将所有元素设为0,表示无边。
  2. addEdge:在无向图中添加边。由于是无向图,边(u,v)(u, v)(u,v) 和 (v,u)(v, u)(v,u) 都设置为1。
  3. printGraph:输出邻接矩阵,方便观察图的结构。

运行上述代码会输出邻接矩阵,通过该矩阵可以查看每个顶点之间是否有边连接。

2. 图的存储结构

图的存储结构影响图的操作效率和存储空间。具体的存储结构包括:


2.1 数组表示法(邻接矩阵)

邻接矩阵是一种顺序存储结构,使用二维数组来表示顶点之间的连接关系。适合用于稠密图(边数接近于顶点数平方)。

  • 定义:假设图有 VVV 个顶点,用一个 V×VV \times VV×V 的二维数组 graph 表示图的邻接矩阵。
  • 有向图:若存在从顶点 iii 到顶点 jjj 的边,则 graph[i][j] = 1
  • 无向图:如果 iii 和 jjj 之间有边,则 graph[i][j] = graph[j][i] = 1
  • 加权图:若有边权重,则使用权重值代替 1。
#include <stdio.h>#define MAX_VERTICES 4void printGraph(int graph[MAX_VERTICES][MAX_VERTICES]) {for (int i = 0; i < MAX_VERTICES; i++) {for (int j = 0; j < MAX_VERTICES; j++) {printf("%d ", graph[i][j]);}printf("\n");}
}int main() {int graph[MAX_VERTICES][MAX_VERTICES] = { {0, 1, 0, 0}, {1, 0, 1, 1}, {0, 1, 0, 1}, {0, 1, 1, 0} };printf("邻接矩阵表示的图:\n");printGraph(graph);return 0;
}

2.2 邻接表

邻接表使用链表来存储顶点的邻接关系,适合用于稀疏图(边数较少)。

  • 定义:用数组 graph 存储每个顶点的邻接链表。graph[i] 指向顶点 iii 的链表,链表中包含所有与顶点 iii 相连的顶点。
  • 优点:节省空间,适合边较少的图。
#include <stdio.h>
#include <stdlib.h>#define MAX_VERTICES 4typedef struct Node {int vertex;struct Node* next;
} Node;Node* createNode(int vertex) {Node* newNode = (Node*)malloc(sizeof(Node));newNode->vertex = vertex;newNode->next = NULL;return newNode;
}void addEdge(Node* graph[], int u, int v) {Node* newNode = createNode(v);newNode->next = graph[u];graph[u] = newNode;newNode = createNode(u);newNode->next = graph[v];graph[v] = newNode;
}void printGraph(Node* graph[]) {for (int i = 0; i < MAX_VERTICES; i++) {Node* temp = graph[i];printf("顶点 %d:", i);while (temp) {printf(" -> %d", temp->vertex);temp = temp->next;}printf("\n");}
}int main() {Node* graph[MAX_VERTICES] = {NULL};addEdge(graph, 0, 1);addEdge(graph, 0, 2);addEdge(graph, 1, 2);addEdge(graph, 2, 3);printf("邻接表表示的图:\n");printGraph(graph);return 0;
}

2.3 十字链表

十字链表是一种用于存储有向图的结构,每条边包含指向起点和终点的两个指针,适合于表示入度和出度。

  • 定义:每个顶点包含两个链表,一个存储出边,一个存储入边。
  • 适用场景:用于需要频繁操作边的有向图。
typedef struct ArcNode {int tailvex, headvex;struct ArcNode *hlink, *tlink;
} ArcNode;typedef struct VNode {int data;ArcNode *firstin, *firstout;
} VNode;VNode graph[MAX_VERTICES];

2.4 邻接多重表

邻接多重表适合用于存储无向图,特别是在边的操作频繁时。每条边的节点结构包括两个顶点及其各自的相邻边指针。

  • 定义:每条边包含两个指针,分别指向两个顶点的邻接表节点。
  • 适用场景:用于表示无向图的连通性。
typedef struct EdgeNode {int ivex, jvex;struct EdgeNode *ilink, *jlink;
} EdgeNode;typedef struct VertexNode {int data;EdgeNode *firstedge;
} VertexNode;VertexNode graph[MAX_VERTICES];

 3. 图的遍历

图的遍历是指从一个顶点出发,沿着边遍历每一个顶点。常用的图的遍历方法有两种:深度优先搜索和广度优先搜索。

3.1 深度优先搜索(DFS)

深度优先搜索是一种尽可能“深入”访问图中顶点的遍历方法。其特点是沿着路径深入到某一分支的末端,然后回溯到上一个节点再遍历其他分支。

DFS 实现代码

以下是一个使用邻接矩阵表示图,并实现DFS的C语言示例代码:

#include <stdio.h>#define MAX_VERTICES 10  // 定义最大顶点数
#define TRUE 1
#define FALSE 0// 定义图结构
typedef struct {int vertices[MAX_VERTICES][MAX_VERTICES];  // 邻接矩阵int vertex_count;  // 顶点数量
} Graph;// 初始化图
void initGraph(Graph *g, int vertex_count) {g->vertex_count = vertex_count;for (int i = 0; i < vertex_count; i++) {for (int j = 0; j < vertex_count; j++) {g->vertices[i][j] = 0;}}
}// 添加边
void addEdge(Graph *g, int start, int end) {g->vertices[start][end] = 1;g->vertices[end][start] = 1;  // 如果是无向图,则双向连接
}// 深度优先搜索
void DFS(Graph *g, int v, int visited[]) {visited[v] = TRUE;  // 标记顶点为已访问printf("%d ", v);   // 输出当前访问的顶点for (int i = 0; i < g->vertex_count; i++) {if (g->vertices[v][i] == 1 && !visited[i]) {  // 找到未访问的相邻顶点DFS(g, i, visited);  // 递归访问相邻顶点}}
}// 调用DFS遍历整个图
void depthFirstSearch(Graph *g) {int visited[MAX_VERTICES] = {FALSE};  // 初始化访问标记printf("深度优先搜索遍历结果: ");for (int i = 0; i < g->vertex_count; i++) {if (!visited[i]) {DFS(g, i, visited);  // 对未访问的顶点进行DFS}}printf("\n");
}

3.2 广度优先搜索(BFS)

广度优先搜索是一种“逐层”访问图中顶点的遍历方法,使用队列存储待访问的顶点。先访问当前层的所有顶点,再逐层深入,适合查找最短路径。

BFS 实现代码

以下代码使用邻接矩阵表示图,并实现BFS算法:

#include <stdbool.h>#define QUEUE_SIZE MAX_VERTICEStypedef struct {int items[QUEUE_SIZE];int front, rear;
} Queue;// 初始化队列
void initQueue(Queue *q) {q->front = 0;q->rear = -1;
}// 入队
void enqueue(Queue *q, int value) {if (q->rear < QUEUE_SIZE - 1) {q->items[++q->rear] = value;}
}// 出队
int dequeue(Queue *q) {if (q->front <= q->rear) {return q->items[q->front++];}return -1;
}// 判断队列是否为空
bool isEmpty(Queue *q) {return q->front > q->rear;
}// 广度优先搜索
void breadthFirstSearch(Graph *g, int start) {int visited[MAX_VERTICES] = {FALSE};Queue q;initQueue(&q);printf("广度优先搜索遍历结果: ");enqueue(&q, start);   // 起点入队visited[start] = TRUE;while (!isEmpty(&q)) {int v = dequeue(&q);  // 获取队首顶点printf("%d ", v);for (int i = 0; i < g->vertex_count; i++) {if (g->vertices[v][i] == 1 && !visited[i]) {enqueue(&q, i);   // 相邻未访问顶点入队visited[i] = TRUE;  // 标记为已访问}}}printf("\n");
}

主函数调用示例

在主函数中初始化图、添加边并调用DFS和BFS函数进行遍历:

int main() {Graph g;initGraph(&g, 5);  // 假设图中有5个顶点// 添加一些边addEdge(&g, 0, 1);addEdge(&g, 0, 2);addEdge(&g, 1, 3);addEdge(&g, 2, 4);depthFirstSearch(&g);  // 执行深度优先搜索breadthFirstSearch(&g, 0);  // 从顶点0执行广度优先搜索return 0;
}

4 图的连通性问题

图的连通性问题在很多算法中是重要的基础。连通性问题主要涉及图的连通分量、生成树、强连通分量、最小生成树等。

4.1 无向图的连通分量和生成树

无向图的连通分量是一个最大连通子图。一个无向图可能包含多个连通分量。生成树是一个连通分量中包含所有顶点的最小边集合,且不包含环。

4.1.1 连通分量的DFS实现

可以通过深度优先搜索(DFS)找到所有连通分量。

#include <stdio.h>#define MAX_VERTICES 10typedef struct {int vertices[MAX_VERTICES][MAX_VERTICES];  // 邻接矩阵int vertex_count;  // 顶点数
} Graph;void DFS(Graph *g, int v, int visited[]) {visited[v] = 1;printf("%d ", v);for (int i = 0; i < g->vertex_count; i++) {if (g->vertices[v][i] == 1 && !visited[i]) {DFS(g, i, visited);}}
}void findConnectedComponents(Graph *g) {int visited[MAX_VERTICES] = {0};printf("连通分量:\n");for (int i = 0; i < g->vertex_count; i++) {if (!visited[i]) {DFS(g, i, visited);  // 每找到一个未访问顶点即代表一个连通分量printf("\n");}}
}

4.1.2 最小生成树的Kruskal算法

Kruskal算法通过对所有边进行排序并逐步选择权值最小的边来构造最小生成树。

4.2 有向图的强连通分量

在有向图中,强连通分量是一个子图,其中任意两个顶点都是互相可达的。通常可以使用Kosaraju算法实现,它包含两次DFS遍历。

// 示例:通过Kosaraju算法找出强连通分量
void kosarajuSCC(Graph *g) {// 1. 执行第一次DFS,记录顶点的完成时间// 2. 反转图// 3. 按照完成时间降序执行第二次DFS,找出所有强连通分量
}

4.3 最小生成树

最小生成树(MST)是一个加权连通图的子图,它连接了所有顶点并且总权值最小。常用的算法有Kruskal和Prim。

4.3.1 Kruskal算法

Kruskal算法基于边的排序实现MST。主要步骤是将图中的边按权重排序,从小到大依次选择,如果加入一条边不构成环,则将其加入MST中。

// Kruskal算法代码示例
void kruskalMST(Graph *g) {// 对所有边按权重排序// 依次选择权重最小且不构成环的边
}

4.4 关节点和重连通分量

关节点(Articulation Point)是指在无向图中去掉该顶点后,图的连通性减少的点。可以通过DFS和时间戳的递归算法(Tarjan算法)找到关节点。

#include <limits.h>void articulationPoints(Graph *g) {int visited[MAX_VERTICES] = {0}, parent[MAX_VERTICES], disc[MAX_VERTICES], low[MAX_VERTICES];for (int i = 0; i < g->vertex_count; i++) {if (!visited[i]) {dfsArticulation(g, i, visited, disc, low, parent);}}
}void dfsArticulation(Graph *g, int u, int visited[], int disc[], int low[], int parent[]) {static int time = 0;visited[u] = 1;disc[u] = low[u] = ++time;int children = 0;for (int v = 0; v < g->vertex_count; v++) {if (g->vertices[u][v]) {  // 如果u和v之间有边if (!visited[v]) {  // v未访问parent[v] = u;children++;dfsArticulation(g, v, visited, disc, low, parent);low[u] = (low[u] < low[v]) ? low[u] : low[v];if (parent[u] == -1 && children > 1) {printf("关节点: %d\n", u);}if (parent[u] != -1 && low[v] >= disc[u]) {printf("关节点: %d\n", u);}} else if (v != parent[u]) {low[u] = (low[u] < disc[v]) ? low[u] : disc[v];}}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/469156.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

函数式编程Stream流(通俗易懂!!!)

目录 1.Lambda表达式 1.1 基本用法 1.2 省略规则 2.Stream流 2.1 常规操作 2.1.1 创建流 2.1.2 中间操作 filter map distinct sorted limit ​编辑skip flatMap 2.1.3 终结操作 foreach count max&min collect anyMatch allMatch noneMatch …

SDL线程

文章目录 SDL线程相关 SDL线程相关 SDL线程创建&#xff1a;SDL_CreateThreadSDL线程等待: SDL_WaitThreadSDL互斥锁 :SDL_CreateMutex/SDL_DestoryMutexSDL锁定互斥: SDL_LockMutex/SDL_UnlockMutexSDL条件变量:SDL_CreateCond/SDL_DestoryCondSDL条件变量 等待通知: SDL_Con…

【初阶数据结构与算法】线性表之链表的分类以及双链表的定义与实现

文章目录 一、链表的分类二、双链表的实现1.双链表结构的定义2.双链表的初始化和销毁初始化函数1初始化函数2销毁函数 3.双链表的打印以及节点的申请打印函数节点的申请 4.双链表的头插和尾插头插函数尾插函数 5.双链表的查找和判空查找函数判空函数 6.双链表的头删和尾删头删函…

深 度 学 习

神经网络基础 一、逻辑回归( Logic Regression ) 1 问题的模型 模型&#xff1a; 其中xx为输入量&#xff0c;y^​预测量&#xff0c;σ()激活函数。   逻辑回归主要用于二分类问题的拟合&#xff1a;0≤y^P(y1∣x)≤1&#xff0c;σ(z)如图&#xff1a; ​ 问题&#xff…

【Leecode】Leecode刷题之路第46天之全排列

题目出处 46-全排列-题目出处 题目描述 个人解法 思路&#xff1a; todo代码示例&#xff1a;&#xff08;Java&#xff09; todo复杂度分析 todo官方解法 46-全排列-官方解法 预备知识 回溯法&#xff1a;一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解…

解线性方程组(二)

实验类型&#xff1a;●验证性实验 ○综合性实验 ○设计性实验 实验目的&#xff1a;进一步熟练掌握用Jacobi迭代法和Gauss-Seidel法解线性方程组的算法&#xff0c;提高编程能力和解算线性方程组问题的实践技能。 实验内容&#xff1a; 1)取初值性x(0)(0,0,0,0)T, 精度要求ε…

ReactPress系列—NestJS 服务端开发流程简介

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎提出宝贵的建议&#xff0c;感谢Star。 NestJS 服务端开发流程简介 NestJS 是一个用于构建高效、可靠和可扩展的服务器端应用程序的框架。它使用 TypeScript&#xff08;但也支持纯 Java…

ImportError: cannot import name ‘packaging‘ from ‘pkg_resources‘ 的参考解决方法

文章目录 写在前面一、问题描述二、解决方法参考链接 写在前面 自己的测试环境&#xff1a; Ubuntu20.04 ROS-Noetic 一、问题描述 自己在通过 pip install 安装module时 &#xff08;使用的是 pip install mmcv&#xff09;遇到如下问题&#xff1a; ImportError: cannot …

运维人员必备的 Mac Zsh 配置技巧

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

Flume学习

一、Flume概述 Flume最主要的作用就是&#xff0c;实时读取服务器本地磁盘的数据&#xff0c;将数据写入到HDFS。 二、Flume基础架构 三、Flume安装部署 配置Flume的前提是要配置好JDK和Hadoop 1.解压 [rootlxm148 soft]# tar -zxvf ./apache-flume-1.9.0-bin.tar.gz -C /…

FBX福币交易所多只高位股重挫,聚星科技首日高开348%

查查配分析11月11日电 周一,A股三大指数集体低开,沪指低开0.58%,深成指低开0.67%,创业板指低开0.99%。 FBX福币凭借用户友好的界面和对透明度的承诺,迅速在加密货币市场中崭露头角,成为广大用户信赖的平台。 Wind截图 券商股明显回调,大消费普遍走低,乳业、白酒、文旅板块跌幅…

基于matlab的人眼开度识别

我国已经成为世界汽车生产和制造大国&#xff0c;道路车辆的不断增加道路基础设施不断增强&#xff0c;但是随之而来的问题也日益严重&#xff0c;比如交通事故&#xff0c;噪声大气污染等。汽车行驶的安全性由于关乎人民生命安全&#xff0c;所以日益受到各国政府以及研究机构…

【数据分享】2024年我国省市县三级的生活服务设施数量(46类设施/Excel/Shp格式)

人才市场、售票处、旅行社等生活服务设施的配置情况是一个城市公共基础设施完善程度的重要体现&#xff0c;一个城市生活服务设施种类越丰富&#xff0c;数量越多&#xff0c;通常能表示这个城市的公共服务水平越高&#xff01; 本次我们为大家带来的是我国各省份、各地级市、…

Node.js——fs模块-文件夹操作

1、借助Node.js的能力&#xff0c;我们可以对文件夹进行创建、读取、删除等操作 2、方法 方法 说明 mkdir/mkdirSync 创建文件夹 readdir/readdirSync 读取文件夹 rmdir/rmdirSync 删除文件夹 3、语法 其余的方法语法类似 本文的分享到此结束&#xff0c;欢迎大家评论区…

C++builder中的人工智能(21):Barabási–Albert model(BA)模型

在此之前&#xff0c;大多数网络被想当然的认为是随机的&#xff0c;因此连接度分布可以近似用泊松分布来表示&#xff0c;而巴拉巴西与其学生阿尔伯特、郑浩雄通过对万维网度分布测量的结果却显示万维网度分布服从幂律分布&#xff0c;存在枢纽节点&#xff08;拥有大量链接的…

ReactPress 安装指南:从 MySQL 安装到项目启动

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎Star。 ReactPress 是一个基于 React 的开源发布平台&#xff0c;适用于搭建博客、网站或内容管理系统&#xff08;CMS&#xff09;。本文将详细介绍如何安装 ReactPress&#xff0c;包括…

从0开始深度学习(25)——多输入多输出通道

之前我们都只研究了一个通道的情况&#xff08;二值图、灰度图&#xff09;&#xff0c;但实际情况中很多是彩色图像&#xff0c;即有标准的RGB三通道图片&#xff0c;本节将更深入地研究具有多输入和多输出通道的卷积核。 1 多输入通道 当输入包含多个通道时&#xff0c;需要…

【C++笔记】C++三大特性之继承

【C笔记】C三大特性之继承 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】C三大特性之继承前言一.继承的概念及定义1.1 继承的概念1.2继承的定义1.3继承基类成员访问方式的变化1.4继承类模板 二.基类和派生类间的转…

【Unity】Game Framework框架学习使用

前言 之前用过一段时间的Game Framework框架&#xff0c;后来有那么一段时间都做定制小软件&#xff0c;框架就没再怎么使用了。 现在要做大型项目了&#xff0c;感觉还是用框架好一些。于是又把Game Framework拾起来了。 这篇文章主要是讲Game Framework这个框架是怎么用的…

Ubuntu 22 安装 Apache Doris 3.0.3 笔记

Ubuntu 22 安装 Apache Doris 3.0.3 笔记 1. 环境准备 Doris 需要 Java 17 作为运行环境&#xff0c;所以首先需要安装 Java 17。 sudo apt-get install openjdk-17-jdk -y sudo update-alternatives --config java在安装 Java 17 后&#xff0c;可以通过 sudo update-alter…