图(graph)的遍历----深度优先(DFS)遍历

目录

前言

深度优先遍历(DFS)

1.基本概念

 2.算法思想

3.二叉树的深度优先遍历(例子) 

图的深度优先遍历

1.图(graph)邻接矩阵的深度优先遍历

思路分析

代码实现

2.图(graph)邻接表的深度优先遍历

思路分析

代码实现

递归代码

非递归代码


前言

        在前面学习过二叉树的时候我们就已经接触到深度优先搜索和广度优先搜索,二叉树的前序遍历和后序遍历都属于深度优先遍历的一种,但是对于二叉树这种有规律的数据结很容易理解,但是如果是对于图这种没有规律的数据结构又该如何去实现深度优先和广度优先遍历呢?下面就一起来看看吧!

深度优先遍历(DFS)

1.基本概念

        深度优先搜索是用来遍历或搜索树和图数据结构的算法,它是可以从任意跟节点开始,选择一条路径走到底,并通过回溯来访问所有节点的算法。简单来说就是通过选择一条道路走到无路可走的时候回退到上一个岔路口,并标记这条路已走过,选择另外一条道路继续走,直到走遍每一条路。(一句话概括:一路走到黑,黑了就回头)

 2.算法思想

Dfs思修基于递归思想,通过递归的形式来缩小问题规模,把一件事分割成若干个相同的小事,逐步完成。

深度优先搜索的步骤分为 1.递归下去 2.回溯上来。顾名思义,深度优先,则是以深度为准则,先一条路走到底,直到达到目标。这里称之为递归下去。

否则既没有达到目标又无路可走了,那么则退回到上一步的状态,走其他路。这便是回溯上来。

3.二叉树的深度优先遍历(例子) 

为了大家更好的了解深度优先遍历的算法,我下面举一个二叉树深度优先遍历的例子,如下图所示的一个二叉树,对其进行前序遍历(深度优先遍历) 

结果为: A B D H I E J C F K G  

前序遍历就是从根结点出发,一直向左子节点走,直到左子节点不存在然后返回到上一个节点走这个节点的右子节点,然后一直往右子节点走,同样的也是走不通为止就返回。很显然这种一路走到黑,黑了就回头的方式,就是深度优先遍历的过程。

图的深度优先遍历

        对于二叉树的深度优先遍历大家都很好理解,但是如果对于图的话,那怎么去进行深度优先遍历呢?

如图所示,拿到这么一个图我们从V1开始对其进行深度优先遍历,那么每一次遇到分叉点走不同的路径遍历到的结果是不一样的,所以对于一个图的深度优先变量结果可以为多种。

深度优先遍历算法步骤

  1. 访问初始结点v,并标记结点v为已访问。
  2. 查找结点v的第一个邻接结点w。
  3. 若w存在,则继续执行4,如果w不存在,则回到第1步,将从v的下一个结点继续。
  4. 若w未被访问,对w进行深度优先遍历递归(即把w当做另一个v,然后进行步骤123)
  5. 查找结点v的w邻接结点的下一个邻接结点,转到步骤3。

在此之前我们学习了图的两种储存方式,分别是邻接矩阵和邻接表,那么这两种存储方式进行深度优先遍历结果会有什么不同呢?我们接着看。 

1.图(graph)邻接矩阵的深度优先遍历

思路分析

如上图所示,从2位置开始进行深度优先遍历,由于图可能是具有环形结构的,为了避免进入到环内的死循环,这里我们需要用到一个辅助数组visited来标记某一个顶点是否遍历过,如果遍历过的话,那么就不走这个方向,如果全部的方向都被标记遍历过,那就返回到上一个位置,换一个方向去遍历。(递归算法)

        对于visited数组,初始化为0,表示未访问过,如果访问了的话那么就设置为1 

最后整个图遍历完成之后,visited数组里面的数组全都为1 ,也就是说这个图已经变量完成了

代码实现
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define Maxint 32767
#define Maxnum 100//最大顶点数//数据类型
typedef struct d { char id[10];//……
}
ElemType;//图的邻接数组
typedef struct graph {ElemType vexs[Maxnum];//图数据int matrix[Maxnum][Maxnum];//二维数组矩阵int vexnum;//点数int arcnum;//边数
}Graph;//节点id查找下标
int Locate_vex(Graph G, char* id) {for (int i = 0; i < G.vexnum; i++)if (strcmp(G.vexs[i].id,id)==0)return i;return -1;
}
//构造邻接矩阵(无向图,对称矩阵)(有向图)赋权图
void Create_graph(Graph* G) {printf("请输入顶点个数和边的个数:\n");scanf("%d %d", &G->vexnum, &G->arcnum);//输入点数边数printf("请输入顶点数据:\n");for (int i = 0; i < G->vexnum; i++) {scanf("%s", G->vexs[i].id);}for (int x = 0; x < G->vexnum; x++) {for (int y = 0; y < G->vexnum; y++) {if (x == y)G->matrix[x][y] = 0;//对角线初始化为0elseG->matrix[x][y] = Maxint;//其他初始化为Maxint}}printf("请输入边相关数据:\n");for (int k = 0; k < G->arcnum; k++) {char a[10], b[10];int w;scanf("%s %s %d", a, b, &w);//a->bint i = Locate_vex(*G, a);int j = Locate_vex(*G, b);//矩阵赋值G->matrix[i][j] = w;G->matrix[j][i] = w;//删掉这个,表示有向图}
}//输出矩阵
void print_matrix(Graph G) {printf("矩阵为:\n");for (int i = 0; i < G.arcnum; i++) {for (int j = 0; j < G.arcnum; j++)printf("%-5d ", G.matrix[i][j]);printf("\n");}printf("图的顶点个数和边数:%d,%d\n", G.vexnum, G.arcnum);
}//访问输出
void visit(Graph G,int loca) {printf("%s ", G.vexs[loca].id);
}
//深度优先遍历DFS
void DFS(Graph G, char* begin,int* visited) {int loca = Locate_vex(G, begin);visit(G, loca);visited[loca] = 1;//访问完后,visited对应的下标标记为1for (int i = 0; i < G.vexnum; i++) {//当前顶点对其他顶点进行遍历,如果有通路就执行访问操作if (G.matrix[loca][i] != 0&& G.matrix[loca][i]!=Maxint )if(!visited[i])//如果visited为0的话(下一个顶点未访问过),那么就进入到下一个顶点继续访问DFS(G, G.vexs[i].id , visited);}return;
}int main() {Graph G;Create_graph(&G);print_matrix(G);int* visited = (int*)malloc(sizeof(int) * G.vexnum);memset(visited, 0, sizeof(int)*G.vexnum);//初始化为0printf("DFS:\n");DFS(G, "B" , visited);
}

输出结果:

2.图(graph)邻接表的深度优先遍历

思路分析

前面我们学习过了邻接表是由两种节点组成的,分别是头结点和边节点,头结点是用来储存数据的,而变节点是用来储存于当前节点有通路的节点的引索。如图所示

对于邻接表的话,那怎么去书写代码呢?同样的我们还是需要一个visited数组来标记已经访问过的节点,然后对下一个节点进行判断是否访问过,如果没有访问过那么就进入到下一个节点的递归,如果访问过那就换一个方向继续访问,如果都访问过,那就返回到上一个节点的位置重复以上的操作。下面我提供两种代码的书写方式,分别是递归和非递归来去实现深度优先遍历。

代码实现

创建图代码(邻接表):

#include<stdio.h>
#include<string.h>//数据结构体
typedef struct datatype {char id[10];//字符串编号//………………
}ElemType;
//边节点存储结构
typedef struct arcnode {int index;//指向顶点的位置int weight;//权struct arcnode* nextarc;//指向下一个边节点
}Anode;
//顶点结点存储结构
typedef struct vexnode {ElemType data;Anode* firstarc;
}Vhead;
//图结构
typedef struct {Vhead* vertices;int vexnum;int arcnum;
}Graph;//顶点id查找下标
int Locate_vex(Graph G, char* id) {for (int i = 0; i < G.vexnum; i++)if (strcmp(G.vertices[i].data.id,id)==0)return i;return -1;
}
//创建头节点
void Create_vexhead(Graph *G,int n) {G->vertices = (Vhead*)malloc(sizeof(Vhead) *n);if (!G->vertices) {printf("ERROR\n");exit(-1);}else {for (int i = 0; i < n ; i++) {scanf("%s", G->vertices[i].data.id);G->vertices[i].firstarc = NULL;}}
}
//创建一个边节点
Anode* Create_arcnode(int loca, int w) {Anode* arc = (Anode*)malloc(sizeof(Anode));if (!arc){printf("ERROR\n");exit(-1);}arc->index = loca;arc->nextarc = NULL;arc->weight = w;return arc;
}
//创建邻接表(无向图)(有向图)
void Create_graph(Graph* G) {printf("输入顶点数和边数:\n");scanf("%d %d", &G->vexnum, &G->arcnum);printf("输入顶点数据:\n");Create_vexhead(G, G->vexnum);printf("输入边数据:\n");for (int k = 0; k <G->arcnum; k++) {ElemType a, b;int w;scanf("%s%s%d", a.id, b.id, &w);int i = Locate_vex(*G, a.id);int j = Locate_vex(*G, b.id);//头插法//a->bAnode* p = Create_arcnode(j, w);p->nextarc = G->vertices[i].firstarc;G->vertices[i].firstarc = p;//如果创建有向图的话,直接把下面的代码删掉即可//b->aAnode* q = Create_arcnode(i, w);q->nextarc = G->vertices[j].firstarc;G->vertices[j].firstarc = q;}
}//访问
void visit(Graph G, int index) {printf("%s ", G.vertices[index].data.id);
}//输出图
void print(Graph G) {printf("以下是图的顶点连接关系:\n");for (int i = 0; i < G.vexnum; i++) {printf("%s:", G.vertices[i].data.id);Anode* cur= G.vertices[i].firstarc;while (cur) {visit(G, cur->index);cur = cur->nextarc;}printf("\n");}printf("顶点和边数分别是:%d %d\n", G.vexnum, G.arcnum);
}
递归代码
//深度优先遍历DFS0(递归实现)
void DFS0(Graph G, char* begin_id,int* visited) {int index = Locate_vex(G, begin_id);visit(G, index);visited[index] = 1;//标记这个顶点已经访问过了Anode* p = G.vertices[index].firstarc;//找到这个顶点的下一个位置pwhile (p) {if (visited[p->index] == 0) {//如果没有访问过的话,就进入到下一个递归DFS0(G, G.vertices[p->index].data.id, visited);}p = p->nextarc;//退出之后换一个方向访问}return;
}
非递归代码
//深度优先遍历DFS(非递归实现)
void DFS(Graph G,char* begin_id) {//判断是否变量过int* visited = (int*)malloc(sizeof(int) * G.vexnum);memset(visited, 0, sizeof(int) * G.vexnum);//全部初始化为0//记录路径int* path = (int*)malloc(sizeof(int) * G.vexnum);memset(path, -1, sizeof(int) * G.vexnum);int i = -1;//初始化int index = Locate_vex(G, begin_id);int count = 0;//表示已经访问了多少个顶点Anode* p= G.vertices[index].firstarc;//初始化下一个位置指针pwhile (count<G.vexnum) {if (p) {if (visited[index] == 0) {visit(G, index);visited[index] = 1;i++;path[i] = index;//记录当前路径p = G.vertices[index].firstarc;index = p->index;//指向下一个位置count++;}else {p = p->nextarc;index = p->index;}}//回溯过程else {index = path[i - 1];//返回到上一个位置p = G.vertices[index].firstarc->nextarc;//p指针向后移动一位index = p->index;i--;}}
}

测试结果:

int main() {Graph G;Create_graph(&G);print(G);printf("\n递归DFS0深度优先遍历:\n");int* visited = (int*)malloc(sizeof(int) * G.vexnum);memset(visited, 0, sizeof(int) * G.vexnum);DFS0(G, "B", visited);printf("\n非递归DFS深度优先遍历:\n");DFS(G, "B");
}

 以上就是本期的全部内容了,我们下次接着学习图的广度优先遍历,下次见咯!

分享一张壁纸: 

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

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

相关文章

京东数据分析:2023年9月京东洗烘套装品牌销量排行榜!

鲸参谋监测的京东平台9月份洗烘套装市场销售数据已出炉&#xff01; 根据鲸参谋平台的数据显示&#xff0c;今年9月份&#xff0c;京东平台洗烘套装的销量为7100&#xff0c;环比下降约37%&#xff0c;同比增长约87%&#xff1b;销售额为6000万&#xff0c;环比下降约48%&#…

Rust-后端服务调试入坑记

这篇文章收录于Rust 实战专栏。这个专栏中的相关代码来自于我开发的笔记系统。它启动于是2023年的9月14日。相关技术栈目前包括&#xff1a;Rust&#xff0c;Javascript。关注我&#xff0c;我会通过这个项目的开发给大家带来相关实战技术的分享。 如果你关注过我的Rust 实战里…

Elasticsearch实践:ELK+Kafka+Beats对日志收集平台的实现

可以在短时间内搜索和分析大量数据。 Elasticsearch 不仅仅是一个全文搜索引擎&#xff0c;它还提供了分布式的多用户能力&#xff0c;实时的分析&#xff0c;以及对复杂搜索语句的处理能力&#xff0c;使其在众多场景下&#xff0c;如企业搜索&#xff0c;日志和事件数据分析等…

后台交互-首页->与后台数据进行交互,wsx的使用

与后台数据进行交互wsx的使用 1.与后台数据进行交互 // index.js // 获取应用实例 const app getApp() const apirequire("../../config/app.js") const utilrequire("../../utils/util.js") Page({data: {imgSrcs:[{"img": "https://cd…

ROI的投入产出比是什么?

ROI的投入产出比是什么&#xff1f; 投入产出比&#xff08;Return on Investment, ROI&#xff09;是一种评估投资效益的财务指标&#xff0c;用于衡量投资带来的回报与投入成本之间的关系。它的计算公式如下&#xff1a; 投资收益&#xff1a;指的是投资带来的净收入&#x…

科学指南针iThenticate自助查重系统重磅上线

科学指南针&#xff0c;一直致力于为科研工作者提供高效、专业的学术支持&#xff0c;近日推出了全新的iThenticate自助查重系统。这一系统的上线&#xff0c;旨在为广大科研工作者提供更加便捷、准确的论文查重服务&#xff0c;进一步规范英文使用&#xff0c;提升科研质量。 …

PyTorch 与 TensorFlow:机器学习框架之战

深度学习框架是简化人工神经网络 (ANN) 开发的重要工具&#xff0c;并且其发展非常迅速。其中&#xff0c;TensorFlow 和 PyTorch 脱颖而出&#xff0c;各自在不同的机器学习领域占有一席之地。但如何为特定项目确定理想的工具呢&#xff1f;本综合指南[1]旨在阐明它们的优点和…

WebService SOAP1.1 SOAP1.12 HTTP PSOT方式调用

Visual Studio 2022 新建WebService项目 创建之后启动运行 设置默认文档即可 经过上面的创建WebService已经创建完成&#xff0c;添加HelloWorld3方法&#xff0c; [WebMethod] public string HelloWorld3(int a, string b) { //var s a b; return $"Hello World ab{a …

机器学习中的核方法

一、说明 线性模型很棒&#xff0c;因为它们易于理解且易于优化。他们受苦是因为他们只能学习非常简单的决策边界。神经网络可以学习更复杂的决策边界&#xff0c;但失去了线性模型良好的凸性特性。 使线性模型表现出非线性的一种方法是转换输入。例如&#xff0c;通过添加特征…

【面试经典150 | 区间】用最少数量的箭引爆气球

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;合并区间 其他语言python3 写在最后 Tag 【合并区间】【排序】【数组】 题目来源 452. 用最少数量的箭引爆气球 题目解读 每个气球都有一个占据x轴的一个范围&#xff0c;在这个范围里射出一只箭就会引爆该气球&…

C++笔记之关于函数名前的取址符

C笔记之关于函数名前的取址符 相关博文&#xff1a;C之指针探究(十一)&#xff1a;函数名的本质和函数指针 code review! 文章目录 C笔记之关于函数名前的取址符一.函数名可以被视为指向函数的地址二.sayHello和&sayHello是不是等同?三.Qt信号与槽中的取地址符& 一…

2023全新小程序广告流量主奖励发放系统源码 流量变现系统

2023全新小程序广告流量主奖励发放系统源码 流量变现系统 分享软件&#xff0c;吃瓜视频&#xff0c;或其他资源内容&#xff0c;通过用户付费买会员来变现&#xff0c;用户需要付费&#xff0c;有些人喜欢白嫖&#xff0c;所以会流失一部分用户&#xff0c;所以就写了这个系统…

Node编写用户登录接口

目录 前言 服务器 编写登录接口API 使用sql语句查询数据库中是否有该用户 判断密码是否正确 生成JWT的Token字符串 配置解析token的中间件 配置捕获错误中间件 完整的登录接口代码 前言 本文介绍如何使用node编写登录接口以及解密生成token&#xff0c;如何编写注册接…

关于 硬盘

关于 硬盘 1. 机械硬盘1.1 基本概念1.2 工作原理1.3 寻址方式1.4 磁盘磁记录方式 2. 固态硬盘2.1 基本概念2.2 工作原理 1. 机械硬盘 1.1 基本概念 机械硬盘即是传统普通硬盘&#xff0c;硬盘的物理结构一般由磁头与盘片、电动机、主控芯片与排线等部件组成。 所有的数据都是…

利用dns协议发起ddos反射攻击

利用DNS服务器发起反射型DDOS&#xff0c;攻击带宽 基本思路&#xff1a; 1、利用any类型的dns查询&#xff0c;可完成发送少量请求数据&#xff0c;获得大量返回数据。 2、将原请求地址改为受害者地址&#xff0c;则dns会向受害者返回大量数据&#xff0c;占用带宽 警告&…

QCC 音频输入输出

QCC 音频输入输出 QCC蓝牙芯片&#xff08;QCC3040 QCC3083 QCC3084 QCC5181 等等&#xff09;支持DAC、I2S、SPDIF输出&#xff0c;AUX、I2S、SPDIF、A2DP 输入 蓝牙音频输入&#xff0c;模拟输出是最常见的方式。 也可以再此基础上动态切换输入方式。 输入方式切换参考 sta…

AD9371 官方例程HDL详解之JESD204B TX侧时钟生成 (一)

AD9371 系列快速入口 AD9371ZCU102 移植到 ZCU106 &#xff1a; AD9371 官方例程构建及单音信号收发 ad9371_tx_jesd -->util_ad9371_xcvr接口映射&#xff1a; AD9371 官方例程之 tx_jesd 与 xcvr接口映射 梳理 AD9371 时钟&#xff0c;理解采样率和各个时钟之间的关系 …

RabbitMQ基础篇 笔记

RabbitMQ 余额支付 同步调用 一步一步的来&#xff0c;支付业务写完后&#xff0c;如果之后加需求&#xff0c;还需要增加代码&#xff0c;不符合开闭原则。 性能上也有问题&#xff0c;openfeign是同步调用&#xff0c;性能太差。 同步调用耦合太多。 同步的优势是可以立…

网站、小程序常见布局样式记录

文章目录 &#x1f380;前言&#xff1a;&#x1f415;网页样式展示小程序&#xff1a;《携程网》&#x1f380;持续更新... &#x1f380;前言&#xff1a; 本篇博客会收藏一些作者见到的网页、小程序页面&#xff0c;目的是用来寻找制作项目网页页面的灵感&#xff0c;有需要…

mysql第一篇---索引

文章目录 mysql第一篇---索引索引的数据结构为什么使用索引&#xff1f;索引的及其优缺点InnoDB中索引的推演常见的索引概念InnoDB的B树索引的注意事项MyISAM中索引方案索引的代价MySQL数据结构选择的合理性 mysql第一篇—索引 索引的数据结构 为什么使用索引&#xff1f; 索…