图——图的遍历(DFS与BFS)

前面的文章中我们学习了图的基本概念和存储结构,大家可以通过下面的链接学习:

图的定义和基本术语

图的类型定义和存储结构

这篇文章就来学习一下图的重要章节——图的遍历。

目录

一,图的遍历定义:

二,深度优先搜索(DFS)

连通图的深度优先遍历

邻接矩阵法实现DFS

邻接表法实现DFS

DFS算法效率分析:

非连通图的深度优先遍历

三, 广度优先搜索(BFS)

邻接矩阵法实现BFS

邻接表法实现BFS

 BFS算法效率分析:

DFS与BFS算法效率比较


一,图的遍历定义:

        从已给的连通图中某一顶点出发,沿着一些边访遍图中所有的顶点,且使每个顶点仅被访问一次,就叫做图的遍历,它是图的基本运算

图遍历的实质:找每个顶点的邻接点的过程。

要进行图的遍历,我们需要知道图的特点,从而用合适,高效的方法来实现遍历。

图有哪些特点?

图中可能存在回路,且图的任一顶点都可能与其它顶点相通,在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点 

那么,如何避免在遍历时重复访问?

解决思路:

设置辅助数组 visited [n ],用来标记每个被访问过的顶点。

        初始状态为0

        被访问,改 visited [i]1,防止被多次访问。

图常用的遍历有两种:

深度优先搜索(Depth First Search,DFS    

广度优先搜索(Breadth First Search,BFS)    

二,深度优先搜索(DFS)

引例:

点亮迷宫中所有的灯,我们会一条道走到头,如果走不动了,再往回退寻找其他没有走过的。

因此我们可以总结DFS的详细归纳:

在访问图中某一起始顶点 v 后,由 v 出发,访问 它的任一邻接顶点 w 1
再从 w 1 出发DFS 邻接 但还 未被访问 过的顶点 w 2
然后再从 w 2 出发,进行类似的访问,
如此进行下去,直至到达所有的邻接顶点都被访问过的顶点 u 为止。
接着,退回一步, 退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。
如果有, 则访问此顶点,之后再从此顶点出发,进行与前述类似的访问。    

         如果没有,再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。

连通图的深度优先遍历

仿树的先序遍历过程

 先根,再左子树,最后右子树。

下面是一个练习:

 它的深度优先搜索结果是:2→1→3→5→4→6

那么,在计算机中我们该如何实现DFS的过程?

邻接矩阵法实现DFS

在上一篇文章中我们学习了图的存储结构的邻接矩阵法,借助一个辅助数组 visited [n ]来保存邻接矩阵的信息。

在编程时,使用邻接矩阵法DFS的实现可以采用递归算法:

void DFS(AMGraph G, int v){        	//图G为邻接矩阵类型 cout<<v;  visited[v] = true;  		//访问第v个顶点for(w = 0; w< G.vexnum; w++)  	//依次检查邻接矩阵v所在的行  if((G.arcs[v][w]!=0)&& (!visited[w]))  DFS(G, w); //w是v的邻接点,如果w未访问,则递归调用DFS 
} 

而下面是完整的c语言使用邻接矩阵法来实现图的深度优先搜索:

注意:

这里我使用了C语言的一个库#include <stdbool.h>,来声明我使用了布尔型数据

大家也可以自己定义布尔类型,true代表1为真,false代表0为假

typedef int bool;
#define true 1
#define false 0

#include <stdio.h>
#include <stdbool.h>#define MAX_VERTICES 100// 定义邻接矩阵和访问标记数组
int graph[MAX_VERTICES][MAX_VERTICES];
bool visited[MAX_VERTICES];
int num_vertices;// 深度优先搜索函数
void dfs(int vertex) {// 标记当前节点为已访问visited[vertex] = true;// 输出当前节点printf("%d ", vertex);// 遍历当前节点的所有邻接节点for (int i = 0; i < num_vertices; i++) {// 如果邻接节点存在且未被访问,则递归调用dfs函数if (graph[vertex][i] == 1 && !visited[i]) {dfs(i);}}
}int main() {// 输入顶点数和边数printf("请输入顶点数和边数:");scanf("%d", &num_vertices);// 输入邻接矩阵printf("请输入邻接矩阵:");for (int i = 0; i < num_vertices; i++) {for (int j = 0; j < num_vertices; j++) {scanf("%d", &graph[i][j]);}}// 输出深度优先遍历结果printf("深度优先遍历结果:");// 初始化访问标记数组for (int i = 0; i < num_vertices; i++) {visited[i] = false;}// 遍历所有节点,如果节点未被访问,则调用dfs函数for (int i = 0; i < num_vertices; i++) {if (!visited[i]) {dfs(i);}}return 0;
}

输入实例:

输出结果:

 

邻接表法实现DFS

我们知道图的存储结构除了邻接矩阵法之外还有邻接表法,那么使用邻接表法能否实现图的深度优先搜索? 

当然可以,同样要借助辅助数组visited [n ]

 使用邻接表法DFS的实现也可以采用递归算法:

void DFS(ALGraph G, int v){        //图G为邻接表类型 cout<<v;  visited[v] = true;          //访问第v个顶点p= G.vertices[v].firstarc;             //p指向v的边链表的第一个边结点 
while(p!=NULL){              	      //边结点非空 w=p->adjvex;               	      //表示w是v的邻接点 if(!visited[w])  DFS(G, w); 	     //如果w未访问,则递归调用DFS p=p->nextarc;                	     //p指向下一个边结点 } 
} 

在实际的编程中,使用邻接表法实现的DFS代码:

#include <stdio.h>
#include <stdlib.h>#define MAX_VERTICES 100typedef struct Node {int vertex;struct Node* next;
} Node;typedef struct Graph {int numVertices;Node* adjLists[MAX_VERTICES];
} Graph;// 添加边函数
void addEdge(Graph* graph, int src, int dest) {// 创建新节点Node* newNode = (Node*)malloc(sizeof(Node));newNode->vertex = dest;newNode->next = graph->adjLists[src];graph->adjLists[src] = newNode;
}// 深度优先搜索函数
void DFS(Graph* graph, int vertex, int visited[]) {// 标记当前节点为已访问visited[vertex] = 1;// 输出当前节点printf("%d ", vertex);// 遍历当前节点的所有邻接节点Node* temp = graph->adjLists[vertex];while (temp) {int connectedVertex = temp->vertex;// 如果邻接节点未被访问,则递归调用DFS函数if (!visited[connectedVertex]) {DFS(graph, connectedVertex, visited);}temp = temp->next;}
}// 深度优先遍历函数
void DFSTraversal(Graph* graph) {// 初始化访问标记数组int visited[MAX_VERTICES] = {0};for (int i = 0; i < graph->numVertices; i++) {// 如果节点未被访问,则调用DFS函数if (!visited[i]) {DFS(graph, i, visited);}}
}int main() {// 创建图Graph* graph = (Graph*)malloc(sizeof(Graph));graph->numVertices = 5;// 初始化邻接表for (int i = 0; i < graph->numVertices; i++) {graph->adjLists[i] = NULL;}// 添加边addEdge(graph, 0, 1);addEdge(graph, 0, 2);addEdge(graph, 1, 3);addEdge(graph, 1, 4);// 输出深度优先遍历结果printf("Depth First Traversal: ");DFSTraversal(graph);return 0;
}

输出结果:

Depth First Traversal: 0 2 1 4 3 

DFS算法效率分析:

邻接矩阵来表示图,遍历图中每一个顶点都要从头扫描该顶点所在行,时间复杂度为O(n2)

邻接表来表示图,虽然有 2e 个表结点,但只需扫描 e 个结点即可完成遍历,加上访问 n个头结点的时间,时间复杂度为O(n+e)

结论:

稠密图适于在邻接矩阵上进行深度遍历;

稀疏图适于在邻接表上进行深度遍历。

非连通图的深度优先遍历

如下图例子,连通分量分开访问,先DFS访问完第一个,再访问第二个。

三, 广度优先搜索(BFS)

同样是点亮迷宫中的灯案例,广度优先搜索没有一条道走到黑,而是每个道都走,一层一层实现遍历。

 简单归纳:

        在访问了起始点v之后,依次访问 v的邻接点

        然后再依次访问这些顶点中未被访问过的邻接点

        直到所有顶点都被访问过为止。

 广度优先搜索是一种 分层 的搜索过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有回退的情况。
  因此,广度优先搜索 不是一个递归 的过程,其算法也不是递归的。

 BFS基本思想:——仿树的层次遍历过程

在计算机中,如何实现BFS?

与DFS相比,除辅助数组visited [n ]外,还需再开一辅助队列。

算法思想:

从图中某个顶点v出发,访问v,并置visited[v]的值为true,然后将v进队。

只要队列不空,则重复下述处理。

           ① 队头顶点u出队。

           ② 依次检查u的所有邻接点w,如果visited[w]的值为false,则访问w,并置visited[w]的值为true,然后将w进队。

同样对于BFS可以使用邻接矩阵或邻接表法来实现。

邻接矩阵法实现BFS

#include <stdio.h>
#include <stdlib.h>#define MAX_SIZE 100// 定义队列结构体
typedef struct {int data[MAX_SIZE]; // 队列数据int front, rear; // 队头和队尾指针
} Queue;// 初始化队列
void initQueue(Queue *q) {q->front = q->rear = 0;
}// 判断队列是否已满
int isFull(Queue *q) {return (q->rear + 1) % MAX_SIZE == q->front;
}// 判断队列是否为空
int isEmpty(Queue *q) {return q->front == q->rear;
}// 入队
void enqueue(Queue *q, int x) {if (isFull(q)) {printf("Queue is full!\n");exit(1);}q->data[q->rear] = x;q->rear = (q->rear + 1) % MAX_SIZE;
}// 出队
int dequeue(Queue *q) {if (isEmpty(q)) {printf("Queue is empty!\n");exit(1);}int x = q->data[q->front];q->front = (q->front + 1) % MAX_SIZE;return x;
}// 广度优先搜索
void BFS(int graph[][MAX_SIZE], int visited[], int start, int n) {Queue q;initQueue(&q);enqueue(&q, start);visited[start] = 1;printf("%d ", start);while (!isEmpty(&q)) {int current = dequeue(&q);for (int i = 0; i < n; i++) {if (graph[current][i] && !visited[i]) {enqueue(&q, i);visited[i] = 1;printf("%d ", i);}}}
}int main() {int n, e;printf("Enter the number of vertices and edges: ");scanf("%d %d", &n, &e);int graph[MAX_SIZE][MAX_SIZE] = {0};int visited[MAX_SIZE] = {0};printf("Enter the edges (u v):\n");for (int i = 0; i < e; i++) {int u, v;scanf("%d %d", &u, &v);graph[u][v] = 1;graph[v][u] = 1; }printf("BFS traversal starting from vertex 0:\n");BFS(graph, visited, 0, n);return 0;
}

 输入案例

输出结果

邻接表法实现BFS

#include <stdio.h>
#include <stdlib.h>// 定义邻接表结构体
typedef struct Node {int vertex;struct Node* next;
} Node;// 定义图结构体
typedef struct Graph {int numVertices;Node** adjLists;
} Graph;// 创建新的节点
Node* createNode(int v) {Node* newNode = malloc(sizeof(Node));newNode->vertex = v;newNode->next = NULL;return newNode;
}// 添加边到邻接表
void addEdge(Graph* graph, int src, int dest) {Node* newNode = createNode(dest);newNode->next = graph->adjLists[src];graph->adjLists[src] = newNode;// 如果是无向图,需要添加反向边newNode = createNode(src);newNode->next = graph->adjLists[dest];graph->adjLists[dest] = newNode;
}// 初始化图
Graph* createGraph(int vertices) {Graph* graph = malloc(sizeof(Graph));graph->numVertices = vertices;graph->adjLists = malloc(vertices * sizeof(Node*));for (int i = 0; i < vertices; i++) {graph->adjLists[i] = NULL;}return graph;
}// BFS遍历图
void BFS(Graph* graph, int startVertex) {int visited[graph->numVertices];for (int i = 0; i < graph->numVertices; i++) {visited[i] = 0;}// 创建一个队列并初始化起始顶点Node* queue = createNode(startVertex);visited[startVertex] = 1;// 当队列不为空时继续遍历while (queue != NULL) {// 打印当前顶点printf("%d ", queue->vertex);// 获取当前顶点的邻接顶点列表Node* temp = graph->adjLists[queue->vertex];// 遍历邻接顶点列表while (temp != NULL) {int adjVertex = temp->vertex;if (!visited[adjVertex]) {// 将未访问过的邻接顶点加入队列并标记为已访问Node* newNode = createNode(adjVertex);newNode->next = queue->next;queue->next = newNode;visited[adjVertex] = 1;}temp = temp->next;}// 弹出队列的第一个元素Node* dequeuedNode = queue;queue = queue->next;free(dequeuedNode);}
}int main() {int vertices = 6;Graph* graph = createGraph(vertices);addEdge(graph, 0, 1);addEdge(graph, 0, 2);addEdge(graph, 1, 3);addEdge(graph, 1, 4);addEdge(graph, 2, 4);addEdge(graph, 3, 5);addEdge(graph, 4, 5);printf("BFS traversal starting from vertex 0: ");BFS(graph, 0);return 0;
}

输出结果:

 BFS算法效率分析:

如果使用邻接矩阵,则BFS对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行( n 个元素),总的时间代价为O(n2)

用邻接表来表示图,虽然有 2e 个表结点,但只需扫描 e 个结点即可完成遍历,加上访问 n个头结点的时间,时间复杂度为O(n+e)

DFS与BFS算法效率比较

空间复杂度相同,都是O(n)(借用了堆栈或队列);

时间复杂度只与存储结构(邻接矩阵或邻接表)有关,而与搜索路径无关。

最后是一个小练习


 图的遍历到此就结束啦,如果文章对你有用的话请点个赞支持一下吧!

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

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

相关文章

应用实践之基于MindNLP+MusicGen生成自己的个性化音乐

前言 MusicGen是基于单个语言模型&#xff08;LM&#xff09;的音乐生成模型&#xff0c;使用文本描述或音频提示生成高质量的音乐样本。它基于Transformer结构&#xff0c;包括文本编码器模型和音频压缩模型&#xff0c;以及一个解码器来预测离散的隐形状态音频token。与传统…

uni-data-select 插件配置接收字段,更改默认的text,value

当后台返回的数据源格式不是如下value,text字段时&#xff0c;需要自定义字段配置 range: [{ value: 0, text: "篮球" },{ value: 1, text: "足球" },{ value: 2, text: "游泳" },], 思路有两个&#xff0c; 思路一&#xff1a;前端遍历更改为…

PE文件(十一)移动导出表和重定位表

移动表的原因 一个PE文件中有很多节&#xff0c;每个节都存储不同的数据。而PE文件中的各种表也都分散存储在这些节当中。此时各种表的信息与程序的代码和数据相互混合在一起&#xff0c;如果我们直接对整个程序进行加密&#xff0c;那系统在初始化程序时就会出问题。比如&…

DHCP原理及配置

目录 一、DHCP原理 DHCP介绍 DHCP工作原理 DHCP分配方式 工作原理 DHCP重新登录 DHCP优点 二、DHCP配置 一、DHCP原理 1 DHCP介绍 大家都知道&#xff0c;现在出门很多地方基本上都有WIFI&#xff0c;那么有没有想过这样一个问题&#xff0c;平时在家里都是“固定”的…

【总结】实际业务场景中锁、事务、异常如何考虑使用?

文章目录 锁处理目的&#xff1a;考虑锁控制思路&#xff1a;生命周期接口并发控制解决方案&#xff1a;测试锁是否生效&#xff1a;模拟多线程并发场景的2种方式&#xff1a; 事务处理目的&#xff1a;考虑事务控制思路&#xff1a;解决方案&#xff1a; 总结 锁处理 目的&am…

利用AI辅助制作ppt封面

如何利用AI辅助制作一个炫酷的PPT封面 标题使用镂空字背景替换为动态视频 标题使用镂空字 1.首先&#xff0c;新建一个空白的ppt页面&#xff0c;插入一张你认为符合主题的图片&#xff0c;占满整个可视页面。 2.其次&#xff0c;插入一个矩形&#xff0c;右键选择设置形状格式…

北京交通大学《深度学习》专业课,实验2-前馈神经网络

1. 源代码 见资源“北京交通大学《深度学习》专业课&#xff0c;实验2-前馈神经网络” 2. 实验内容 &#xff08;1&#xff09;手动实现前馈神经网络解决上述回归、二分类、多分类任务 分析实验结果并绘制训练集和测试集的loss曲线 &#xff08;2&#xff09;利用to…

keepalive:

keepalive&#xff1a; 调度器的高可用 vip地址在主备之间的切换&#xff0c;主在工作时&#xff0c;vip地址只在主上&#xff0c;主停止工作&#xff0c;vip漂移到备服务器。 在主备的优先级不变的情况下&#xff0c;主恢复工作&#xff0c;vip会飘回到主服务器。 1、配优…

企业网络运维-给华为交换机配置sftp,浏览交换机文件并下载上传

文章目录 需求实验开户stelnet权限已完成stelnet账号下的sftp配置使用xshell-sftp访问 需求 浏览交换机文件并下载上传 实验 开户stelnet权限 参考https://blog.csdn.net/xzzteach/article/details/140419150 已完成stelnet账号下的sftp配置 服务类型all包括stelnet和sf…

强化学习编程实战-5 基于时间差分的方法

第4章中&#xff0c;当模型未知时&#xff0c;由于状态转移概率P未知&#xff0c;动态规划中值函数的评估方法不再适用&#xff0c;用蒙特卡洛的方法聘雇值函数。 在蒙特卡洛方法评估值函数时&#xff0c;需要采样一整条轨迹&#xff0c;即需要从初始状态s0到终止状态的整个序列…

探索“搭旅万物皆可搭”小程序——构建旅行搭伴平台的创新实践

摘要 随着旅游市场的不断发展和个性化需求的日益增长&#xff0c;旅行搭伴平台逐渐成为连接志同道合旅者的桥梁。本文旨在介绍“搭旅万物皆可搭”小程序的设计理念、核心功能及其背后的技术实现&#xff0c;探讨如何通过算法优化、安全保障、社交互动等手段&#xff0c;打造一…

GUI界面开发之tkinter(一)

Tkinter是一个内置的Python库&#xff0c;用于创建图形用户界面&#xff08;GUI&#xff09;。它提供了一组工具和小部件&#xff0c;用于创建窗口、对话框、按钮、菜单和其他GUI元素。 在本篇文章中&#xff0c;主要介绍了窗口等知识点。 大家好&#xff01;我是码银&#x1…

《昇思25天学习打卡营第22天|onereal》

文本解码原理--以MindNLP为例 回顾&#xff1a;自回归语言模型 根据前文预测下一个单词 一个文本序列的概率分布可以分解为每个词基于其上文的条件概率的乘积 &#x1d44a;_0:初始上下文单词序列&#x1d447;: 时间步当生成EOS标签时&#xff0c;停止生成。 MindNLP/huggi…

MySQL 时区问题:设置了 my.ini 并重启了服务,依旧是 0 时区

1、问题再现 在撰写 飞书 API 2-5 时&#xff0c;需要新建一些数据表&#xff0c;以便实施从数据库到多维表的数据同步。我建了2个测试数据表&#xff0c;连表查询之后&#xff0c;将时间戳转为时间格式返回&#xff0c;结果发现少了 8 小时。 具体逻辑抽象为以下&#xff0c…

S7-1200PLC 2轴直线插补(详细方案对比)

1、V90速度轴应用 速度轴V90PN总线伺服梯形加减速速度控制(标准报文1应用)_v90伺服加减速时间怎么调整-CSDN博客文章浏览阅读288次。SMART PLC斜坡函数SMART PLC斜坡函数功能块(梯形图代码)_RXXW_Dor的博客-CSDN博客斜坡函数Ramp的具体应用可以参看下面的文章链接:PID优化系…

数据库-MySQL 实战项目——书店图书进销存管理系统数据库设计与实现(附源码)

一、前言 该项目非常适合MySQL入门学习的小伙伴&#xff0c;博主提供了源码、数据和一些查询语句&#xff0c;供大家学习和参考&#xff0c;代码和表设计有什么不恰当还请各位大佬多多指点。 所需环境 MySQL可视化工具&#xff1a;navicat&#xff1b; 数据库&#xff1a;MySq…

[笔记] SEW的振动分析工具DUV40A

1.便携式振动分析仪 DUV40A 文档编号&#xff1a;26871998/EN SEW是一家国际化的大型的机械设备供应商。产品线涵盖电机&#xff0c;减速机&#xff0c;变频器等全系列动力设备。DUV40A是他自己设计的一款振动分析工具。 我们先看一下它的软硬件参数&#xff1a; 内置两路传…

防火墙综合实验之NAT和智能选路

目录 前言&#xff1a; 一、实验题目 二、实验操作 需求一 需求二 需求三 需求四、需求五 需求六 需求七 ​编辑 需求八 需求九 需求十 需求十一 三、需求测试 前言&#xff1a; 本篇文章是延续上一篇文章&#xff0c;简单来说就是防火墙实验的完善和延续&#…

CV07_深度学习模块之间的缝合教学(2)--维度转换

教学&#xff08;1&#xff09;&#xff1a;链接 1.1 预备知识 问题&#xff1a;假如说我们使用的模型张量是三维的&#xff0c;但是我们要缝合的模块是四维的&#xff0c;应该怎么办&#xff1f; 方法&#xff1a;pytorch中常用的函数&#xff1a;(1)view函数&#xff08;2…

新华三H3CNE网络工程师认证—DHCP使用场景

网络服务与应用当中的技术有DHCP、Telnet和FTP。DHCP是计算机当中常用来获取地址的。比如日常使用中&#xff0c;计算机并没有接入IP&#xff0c;IP通过DHCP技术从上端服务去获取的。手动配置网络参数会出现多种问题。 文章目录 一、手动配置网络参数的问题1、参数多、理解难2、…