图的遍历——深度优先搜索(DFS)与广度优先搜索(BFS)(附带C语言源码)

news2024/11/15 4:22:21

个人主页:【😊个人主页】
系列专栏:【❤️数据结构与算法】
学习名言:天子重英豪,文章教儿曹。万般皆下品,惟有读书高——《神童诗劝学》


在这里插入图片描述


系列文章目录

第一章 ❤️ 学前知识
第二章 ❤️ 单向链表
第三章 ❤️ 递归


文章目录

  • 系列文章目录
  • 前言
  • 深度优先搜索 (DFS)
  • 算法原理
  • 代码实现(C语言)
  • 广度优先搜索
  • 算法原理
  • 代码实现(C语言)


前言

在此之前我们学习过了图的一些基本概念,如同在二叉树中我们有前序遍历,中序遍历,后序遍历一般,在图中也有两种特殊的遍历方式——深度优先遍历与广度优先遍历


深度优先搜索 (DFS)

深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。

深度优先搜索是一种在开发爬虫早期使用较多的方法。它的目的是要达到被搜索结构的叶结点(即那些不包含任何超链的HTML文件) 。在一个HTML文件中,当一个超链被选择后,被链接的HTML文件将执行深度优先搜索,即在搜索其余的超链结果之前必须先完整地搜索单独的一条链。深度优先搜索沿着HTML文件上的超链走到不能再深入为止,然后返回到某一个HTML文件,再继续选择该HTML文件中的其他超链。当不再有其他超链可选择时,说明搜索已经结束。

深度优先搜索沿着一条路径一直搜索下去,在无法搜索时,回退到刚刚访问过的节点。深度优先遍历是按照深度优先搜索的方式对图进行遍历的。

算法原理

深度优先遍历图的方法是,从图中某顶点v出发:

  1. 访问顶点v;
  2. 依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
  3. 若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。

在dfs的方法中传入一个表示当前顶点序号的参数,在dfs方法内部使用一个for循环来检查当前顶点连接的其他邻居,在递归方法中每访问一个节点那么我们就可以使用输出语句来进行输出,输出结果为深度优先搜索访问节点的顺序

搜索算法简而言之就是穷举所有可能情况并找到合适的答案,所以最基本的问题就是罗列出所有可能的情况,这其实就是一种产生式系统。
我们将所要解答的问题划分成若干个阶段或者步骤,当一个阶段计算完毕,下面往往有多种可选选择,所有的选择共同组成了问题的解空间,对搜索算法而言,将所有的阶段或步骤画出来就类似是树的结构。
我们使用递归实现DFS。首先访问起点s,并标记它已经被访问。然后遍历s的邻居节点,如果某个邻居节点w未被访问过,就递归地访问w。这个递归过程会使得我们从s开始尽可能地往下深入,直到遇到死胡同才会回溯到上一个节点继续遍历其他未访问的邻居。执行完所有的递归操作后,我们就完成了整个图的深度遍历。

所有的搜索算法从其最终的算法实现上来看,都可以划分成两个部分──控制结构和产生系统。
在这里插入图片描述

代码实现(C语言)

#include <stdio.h>
#include <stdlib.h>

#define MAX_VERTEX_NUM 100 // 定义最大的顶点数目

// 定义一个图的数据结构(使用邻接表表示)
typedef struct Graph {
    int V; // 顶点数目
    int** adj_list; // 邻接表
} Graph;

// 创建一条边
int* create_edge(int w) {
    int* edge = (int*) malloc(sizeof(int));
    *edge = w;
    return edge;
}

// 构造函数
Graph* create_graph(int V) {
    Graph* G = (Graph*) malloc(sizeof(Graph));
    G->V = V;
    G->adj_list = (int**) malloc(V * sizeof(int*));
    for (int i = 0; i < V; i++) {
        G->adj_list[i] = NULL;
    }
    return G;
}

// 添加一条边
void add_edge(Graph* G, int v, int w) {
    G->adj_list[v] = create_edge(w);
}

// 释放图的内存
void free_graph(Graph* G) {
    for (int i = 0; i < G->V; i++) {
        if (G->adj_list[i] != NULL) {
            free(G->adj_list[i]);
        }
    }
    free(G->adj_list);
    free(G);
}

// 深度优先搜索算法
void dfs_helper(Graph* G, int v, int visited[]) {
    visited[v] = 1;
    printf("%d ", v);
    int* adj = G->adj_list[v];
    while (adj != NULL) {
        int w = *adj;
        if (visited[w] == 0) {
            dfs_helper(G, w, visited);
        }
        adj = adj + 1;
    }
}

void dfs(Graph* G) {
    // 初始化visited数组
    int visited[MAX_VERTEX_NUM];
    for (int i = 0; i < G->V; i++) {
        visited[i] = 0;
    }
    // 对每个未被访问的顶点调用dfs_helper函数
    for (int i = 0; i < G->V; i++) {
        if (visited[i] == 0) {
            dfs_helper(G, i, visited);
        }
    }
}

int main() {
    Graph* G = create_graph(6);
    add_edge(G, 0, 1);
    add_edge(G, 0, 2);
    add_edge(G, 1, 3);
    add_edge(G, 2, 3);
    add_edge(G, 2, 4);
    add_edge(G, 3, 5);
    dfs(G);
    free_graph(G);
    return 0;
}

注`在这里插入代码片`意,此代码示例中使用一个较简单的图数据结构表示。具体实现时,可以根据实际需要自定义图的数据结构和相关操作。

广度优先搜索

广度优先算法(Breadth-First Search),同广度优先搜索,又称作宽度优先搜索,或横向优先搜索,简称BFS,是一种图形搜索演算法。简单的说,BFS是从根节点开始,沿着树的宽度遍历树的节点,如果发现目标,则演算终止。广度优先搜索的实现一般采用open-closed表

BFS是一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。BFS并不使用经验法则算法。
从算法的观点,所有因为展开节点而得到的子节点都会被加进一个先进先出的伫列中。一般的实作里,其邻居节点尚未被检验过的节点会被放置在一个被称为open的容器中(例如伫列或是链表),而被检验过的节点则被放置在被称为closed的容器中。(open-closed表)

算法原理

所谓广度,就是一层一层的,向下遍历,层层堵截

  1. 访问顶点vi ;
  2. 访问vi 的所有未被访问的邻接点w1 ,w2 , …wk ;
  3. 依次从这些邻接点(在步骤②中访问的顶点)出发,访问它们的所有未被访问的邻接点; 依此类推,直到图中所有访问过的顶点的邻接点都被访问;

BFS使用一个队列来维护当前正在遍历的节点,首先将起点加入队列,然后只要队列不为空,就从队列中取出一个节点进行拓展(即获取它的邻居节点),依次将这些邻居节点加入队列中。因此,BFS按照离起点的距离逐层遍历,可以求出起点到每个节点的最短路径。
我们先定义了一个队列来维护当前正在遍历的节点,使用数组实现即可。我们还需要记录每个节点是否已经被访问,使用一个数组visited来实现。在BFS算法中,我们每次取出队列的头部节点进行拓展,将其邻居节点加入队列的尾部,并标记它们已经被访问。执行完这些拓展操作后,我们继续从队列头部取出下一个节点进行拓展,直到队列为空。

代码实现(C语言)

#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTEX_NUM 100 // 定义最大的顶点数目

// 定义一个图的数据结构(使用邻接表表示)
typedef struct Graph {
    int V; // 顶点数目
    int** adj_list; // 邻接表
} Graph;

// 创建一条边
int* create_edge(int w) {
    int* edge = (int*) malloc(sizeof(int));
    *edge = w;
    return edge;
}

// 构造函数
Graph* create_graph(int V) {
    Graph* G = (Graph*) malloc(sizeof(Graph));
    G->V = V;
    G->adj_list = (int**) malloc(V * sizeof(int*));
    for (int i = 0; i < V; i++) {
        G->adj_list[i] = NULL;
    }
    return G;
}

// 添加一条边
void add_edge(Graph* G, int v, int w) {
    G->adj_list[v] = create_edge(w);
}

// 释放图的内存
void free_graph(Graph* G) {
    for (int i = 0; i < G->V; i++) {
        if (G->adj_list[i] != NULL) {
            free(G->adj_list[i]);
        }
    }
    free(G->adj_list);
    free(G);
}

// 广度优先搜索算法
void bfs(Graph* G, int s) {
    int visited[MAX_VERTEX_NUM] = {0}; // 记录每个节点是否已经被访问
    int queue[MAX_VERTEX_NUM], head = 0, tail = 0; // 使用队列来存储正在遍历的节点
    visited[s] = 1;
    queue[tail++] = s;
    while (head < tail) {
        int v = queue[head++];
        printf("%d ", v);
        int* adj = G->adj_list[v];
        while (adj != NULL) {
            int w = *adj;
            if (visited[w] == 0) {
                visited[w] = 1;
                queue[tail++] = w;
            }
            adj = adj + 1;
        }
    }
}

int main() {
    Graph* G = create_graph(6);
    add_edge(G, 0, 1);
    add_edge(G, 0, 2);
    add_edge(G, 1, 3);
    add_edge(G, 2, 3);
    add_edge(G, 2, 4);
    add_edge(G, 3, 5);
    bfs(G, 0);
    free_graph(G);
    return 0;
}

在这里插入图片描述

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

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

相关文章

mysql数据迁移与同步常用解决方案总结

目录 一、前言 二、数据迁移场景 2.1 整库迁移 2.2 表数据迁移 2.3 mysql版本变更 2.4 mysql数据迁移至其他存储介质 2.5 自建数据到上云环境 2.6 mysql数据到其他国产数据库 三、数据库物理迁移实施方案 3.1 数据库物理迁移概述 3.1.1 物理迁移适用场景 3.1.2 物理…

杂记 2023.5.10

目录 韦伯和斯托亚科维奇是谁&#xff1f; 介绍一下kali FastDFS和Sentinel是什么&#xff1f; Inferno 找工作的影响因素 1. 背景&#xff1a; 2. 学习过程&#xff1a; 2.1 计算机基础&#xff1a; 2.2 语言&#xff1a; 2.3 数据库等&#xff1a; 2.4 JVM&#…

月薪17k需要什么水平?98年测试员的面试全过程…

我的情况 大概介绍一下个人情况&#xff0c;男&#xff0c;本科&#xff0c;三年多测试工作经验&#xff0c;懂python&#xff0c;会写脚本&#xff0c;会selenium&#xff0c;会性能&#xff0c;然而到今天都没有收到一份offer&#xff01;从年后就开始准备简历&#xff0c;年…

Linux操作系统如何查看CPU型号信息?一条命令搞定

Linux操作系统服务器如何查看CPU处理器信息&#xff1f;使用命令cat /proc/cpuinfo可以查看CPU详细信息&#xff0c;包括CPU核数、逻辑CPU、物理CPU个数、CPU是否启用超线程等&#xff0c;阿里云服务器网分享Linux服务器查看CPU信息命令&#xff1a; 目录 Linux服务器查看CPU…

Visual Studio Code 1.78 发布!

欢迎使用 Visual Studio Code 2023 年 4 月版。一些主要亮点包括&#xff1a; 辅助功能改进 - 更好的屏幕阅读器支持、新的音频提示。新颜色主题 - “现代”浅色和深色主题默认设置。配置文件模板 - Python、Java、数据科学等的内置模板。拖放选择器 - 选择您希望如何将项目链…

【AI】YOLOV2原理详解

1、简介 Yolov2采用了Darknet-19特征提取网络,包括19个卷积层和5个maxpooling层,网络结构如下: 也有尝试使用ResNet-50作为特征提取的模型,网络结构如下: 2、YOLOV2的改进 2.1 加入批归一化(Batch Nomalization) 对数据进行预处理(统一格式、均衡化、去噪等)…

隐语团队研究成果再创佳绩,两篇论文分别被USENIX ATC‘23和IJCAI‘23接收!

‍“USENIX ATC‍年度技术会议”&#xff08;USENIX ATC&#xff0c;USENIX Annual Technical Conference&#xff09;是计算机系统领域的顶级学术会议之一。本年度 USENIX ATC’23将于7月10日至12日在美国波士顿召开。本次会议共投稿353篇论文&#xff0c;接收65篇&#xff0c…

【Redis】电商项目秒杀问题之超卖问题与一人一单问题

目录 一、超卖问题 1、背景 2、产生原因以及线程安全问题 3、解决 1.悲观锁 2.乐观锁 4、新的问题 5、解决 二、一人一单 1、背景 2、产生原因以及线程安全问题 3、解决 4、新的问题----集群下的并发安全问题 5、解决 三、集群下的并发问题 1、说明 2、解决 一…

如何将ChatGPT训练成某个领域的专家!附完整示例!

背景 最近听了 八叉的一个ChatGPT讲座&#xff0c;讲的是如何将ChatGPT训练成领域专家&#xff0c;这样我们就可以用它来解决该领域的各种问题。 整个讲座中最让我印象深刻的就是训练方法&#xff0c;它是通过让ChatGPT向我们提问&#xff0c;然后由我们给出答案的方式进行训…

牛客 BM40 重建二叉树

描述 给定节点数为 n 的二叉树的前序遍历和中序遍历结果&#xff0c;请重建出该二叉树并返回它的头结点。 例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}&#xff0c;则重建出如下图所示。 提示: 1.vin.length pre.length 2.pre 和 vin 均无重复元素…

Redis未授权漏洞复现

Redis简介 Redis是C语言开发的一个开源高性能&#xff08;key-value&#xff09;键值对类型的内存NoSQL数据库&#xff0c;可以用作数据库、缓存、信息中间件(性能非常优秀&#xff0c;支持持久化到硬盘且高可用)。由于其自身特点&#xff0c;可以广泛应用在数据集群&#xff…

threeJs进阶 让模型沿着指定轨迹移动与转向

效果图&#xff1a; 涉及相关知识点&#xff1a; 欧拉对象和四元数主要用来表达对象的旋转信息。 关键词&#xff1a;欧拉Euler、四元数Quaternion、矩阵Matrix4 欧拉对象Euler 欧拉角描述一个旋转变换&#xff0c;通过指定轴顺序和其各个轴向上的指定旋转角度来旋转一个物体…

小红书达人等级有哪些,达人种草力度判断

小红书对于产品及品牌的传播作用&#xff0c;来自于达人自身的分享。以笔记为媒介&#xff0c;对产品进行情景化展示&#xff0c;从而吸引消费&#xff0c;就被称作是种草。而种草力度的强弱&#xff0c;则与达人等级息息相关。下面&#xff0c;就来跟详细为大家解读。 一、小红…

VHDL的基本语法(一)

1 VHDL基本结构 1 实体 Entity&#xff1a;描述所设计的系统的外部接口信号&#xff0c;定义电路设计中所有的输入和输出端口 2 结构体 Architecture&#xff1a;描述系统内部的结构和行为 3 包集合 package&#xff1a;存放各设模块能共享的数据类型、常数和子程序等&#xf…

一百一十、Hive时间转换——from_unixtime踩坑(不要用from_unixtime,而是用from_utc_timestamp)

1.详情 从kettle转换任务得到时间戳为13位&#xff0c;1683701579457。想看看这个时间戳与createTime字段的关系&#xff0c;于是一开始使用了from_unixtime&#xff0c;结果踩坑了 2.运行问题&#xff08;晚8个小时&#xff09; hive> select from_unixtime(cast(1683701…

产品经理怎样活着走出需求评审会?

需求评审是产品经理工作的重要环节&#xff0c;是团队成员间衔接需求的重要桥梁&#xff0c;产品经理的方案能准确落地的重要保障。 一场成功的需求评审会&#xff0c;是能够完整清晰传递产品目标、产品功能&#xff0c;能获得团队认同&#xff0c;并且会后团队能够配合实施的…

orin配置系统

查看linux下的opencv安装版本&#xff1a; pkg-config --modversion opencv查看linux下的opencv安装路径&#xff1a; sudo find / -iname "*opencv*"可知opencv安装在/usr/local/lib里面。 在~/.bashrc中配置如下 在刷机完成的Orin&#xff0c;执行如下命令以安装…

uboot下内存操作mw和md命令详解

mw简介 u-boot 中的 mw 命令是用于向内存写入数据的命令,它有4种形式: mw.b - 写入 1 个字节(8 比特)的数据mw.w - 写入 1 个字(2 字节,16 比特)的数据mw.l - 写入 1 个长字(4 字节,32 比特)的数据mw.q - 写入 1 个四字(8 字节,64 比特)的数据 它们的语法格式是: mw.b addres…

servlet的运行原理

Servlet在容器中的执行过程 1.浏览器向服务器发出GET请求 2.服务器上的Tomcat接收到该url,根据该url判断为Servlet请求&#xff0c;此时Tomcat将产生两个对象&#xff1a;请求对象(HttpServletRequest)和响应对象(HttpServletResponce) 3.Tomcat根据url找到目标Servlet,且创建…

kubernetes详细介绍

kubernetes组件 1 kubernetes组件2 kubernetes概念3 Pod3.1 pod的生命周期1. Pod会出现5种状态2. pod的创建过程3. pod的终止过程 3.2 Pod控制器1. 什么是Pod控制器2. ReplicaSet(RS)3. Deployment4. Horizontal Pod Autoscaler(HPA)5. DaemonSet6. Job7. Cronjob 4 Service4.1…