图搜索基础-深度优先搜索

news2025/1/13 19:58:58

图搜索基础-深度优先搜索

  • 参考
  • 原理
      • 引入
      • 流程解析
      • 手推例子
  • 代码实现
    • 运行结果
    • 结果分析

参考

理论参考:深蓝学院
实现参考:github项目

原理

引入

对于这样一个图,我们试图找到S到G的通路:
在这里插入图片描述
计算机程序不会像人眼一样,一下子连出一条从S到G的通路,需要一个一个节点的访问。每个节点,在建模和编程的时候都设计为一个数据结构,可以知道跟他直接连通的有哪些节点,以及这些边的代价。
我们假想自己就是程序,在访问一个节点,比如S时,看到S和p,d,e三个节点都有边。那我就可以先看看p是否和G有边,或者先看看d是否和G有边,也可以先看看e是否和G有边,这个暂时不重要。假设我们先看p是否和G有边,我们发现p和q有边,和G无边。这时候我们面临了关键选择:我是沿这条线继续深入,看看q是否和G有边哪?还是回过头开另一条线,看看d是否和G有边?这里的选择就是深度优先和广度优先的全部区别所在。

因为可以认为q是p的下一级节点,继续查看p,就是往深处探寻。就像是面对岔路口一样,我是继续选一个岔路往前走还是回到上一个岔路口,深度优先选择选一个岔路口继续深入,而且每次面对这样的选择都这么做。如果一直走到了死胡同也没到终点,再回到上一个岔路口,换一条路继续深入。

根据这个树形结构,可以更好理解,深度:
在这里插入图片描述
你要说,明明是图结构,为什么又变成树结构?回答是,程序面对岔路口时,并不知道地图全貌(他知道,但他不理解)。

主要的找通路问题解决了,还有两个次要问题:

  1. 我们找通路是为了下次走方便的,所以要记录下来这条通路经过了哪些节点。其实我们只需要记录每一个节点的父节点,只要每个节点只有一个父节点,最后一定能回溯出唯一的一条通路
  2. 我们通常不仅是要找通路,更是要找最短路径。在面临一个节点有多个父节点时,需要比较不同父节点时从起点到这个节点的累计代价和,取代价和最小的父节点为父节点。

流程解析

我们根据图搜索的一个通用模板进行流程解析:

  1. 初始化节点数据结构(节点,父节点,累计代价和)。

  2. 初始化开放列表openlist,把初始节点s包含其中,无父节点,累计代价和为0。

  3. 初始化封闭列表closelist,没有任何东西。

  4. 执行以下循环,直到所有节点被访问或通路被找到,或其他结束标准:
    3.1. 根据算法规则,从openlist中取出一个节点。
    3.2. 根据图结构,获得该节点的相邻节点(和该节点有边的节点),并排除掉在closelist中的节点
    3.3 领域查询:

    • 如果相邻节点不在openlist中,以该节点为父节点,计算累计代价值,将此相邻节点直接加入openlist。
    • 如果相邻节点在openlist中,则假设以该节点为父节点,计算此时累计代价值,并与openlist中的历史累计代价值比较:
      • 如果此时累计代价值大,则不加入openlist。
      • 反之将之前的剔除openlist,把此时的加入openlist。

    3.4. 把该节点放入closelist。
    3.5. 判断该节点是否为终止节点。

  5. 终止节点的累计代价就是整条通路的累计代价;从终止节点开始查询父节点就找到了从初始节点到终止节点的整条通路。

图搜索的核心在于3.1的算法规则。对于深度优先算法,我们应该提取最近加入的节点。
举个例子就是:当我们面对岔路时,我们应该走刚看到的岔路中的一条,而不是之前看到的岔路中的一条。
这个思想直接对应于一种数据结构:堆栈。所以用堆栈管理openlist就可以实现深度优先搜索。

当然,此时还有一个小小的问题:

  • 当前节点的相邻节点有好几个,他们应该以什么样的顺序压入堆栈哪?毕竟越晚被压入,越早被取出嘛,压入先后是有不同的。这点随意。比如面对一个岔路,可以顺时针编号,可以逆时针编号,这部分的优化不是深度优先算法的工作。

手推例子

道理明白了,我们结合流程,手推一遍算法的实现:
参照这个图,假设起点为S,重点为r(不用G是因为搜索过程太长了):
在这里插入图片描述

  1. 初始化节点数据结构(节点,父节点,累计代价和)。
  2. 初始化开放列表openlist,把初始节点s包含其中,无父节点,累计代价和为0。
  3. 初始化封闭列表closelist,没有任何东西。
  4. 开始循环:
    a. 弹出openlist最上层节点S;查找到邻居节点d,e,p,都不在openlist和closelist中,因此依次放进openlist,此时openlist为p,e,d;将节点S放入closelist;S没有终止节点r,继续;
    b. 弹出openlist最上层节点d,查找到邻居节点e,c,b,openlist中已经有e,但这个例子中没有定义代价,所以无法判断是否替换掉原有的e,暂时不管,依次放进openlist,此时openlist为p,e(s),e(d),c,b;将节点d放入closelist;d没有终止节点r,继续;
    c. 弹出openlist最上层节点b,查找到邻居节点a,放进openlist,此时openlist为p,e(s),e(d),c,a;将节点b放入closelist;b没有终止节点r,继续;
    d. 弹出openlist最上层节点a,查找到没有邻居节点,什么都不放入openlist,此时openlist为p,e(s),e(d),c;将节点a放入closelist;a没有终止节点r,继续;
    e. 弹出openlist最上层节点c,查找到邻居节点a,放进openlist,此时openlist为p,e(s),e(d),a;将节点c放入closelist;c没有终止节点r,继续;
    f. 弹出openlist最上层节点a,查找到没有邻居节点,此时openlist为p,e(s),e(d);将节点a放入closelist;发现a没有终止节点r,继续;
    g. 弹出openlist最上层节点e(d),查找到邻居节点h,r,放进openlist,此时openlist为p,e(s),h,r;将节点e(d)放入closelist;发现e(d)没有终止节点r,继续;
    h. 弹出openlist最上层节点r,查找到邻居节点f,放进openlist,此时openlist为p,e(s),h,f;把r放进closelist,r就是终止节点,循环结束
  5. r父节点是e(d),e(d)父节点是d,d父节点是s,因此通路为s-d-e-r。因为没有定义代价,所以通路累计代价值不知道。

代码实现

核心代码:

class DFS(AStar):
    """DFS add the new visited node in the front of the openset
    """
    def searching(self):
        """
        Depth-first Searching.
        :return: path, visited order
        """
        # 初始化节点数据结构(在其他文件中定义过了)
            # 因为是栅格地图,节点标识就用坐标标识了
            # 节点的父节点,由self.PARENT列表维护
            # 到达此节点的累计代价值,由self.g列表维护

        # 初始化Openlist
        self.PARENT[self.s_start] = self.s_start    # 维护节点的父节点;起始点父节点是自己
        self.g[self.s_start] = 0                    # 到达此节点的累计代价值;起始点累计代价值为0
        self.g[self.s_goal] = math.inf
        heapq.heappush(self.OPEN, (0, self.s_start))# 把起始点压入openlist

        # 初始化Closelist(在基类中定义过了)
            # 和初始的Openlist一样,是个空列表

        # 循环直到所有节点被遍历完(Openlist空掉)
        while self.OPEN:
            # 弹出最近压入Openlist堆栈的节点
            _, s = heapq.heappop(self.OPEN)
            # 将弹出节点加入Closelist,不再访问
            self.CLOSED.append(s)

            # 如果当前节点就是终点,跳出循环
            if s == self.s_goal:
                break
            
            # 如果当前节点不是终点,进行邻域查询
            for s_n in self.get_neighbor(s):
                # 检查该邻域点是否在closelist中(他给漏掉了,我加上去:)
                if s_n in self.CLOSED:
                    continue

                # 以当前节点为父节点的邻域节点的累计代价值 = 当前节点的累计代价值 + 从当前节点到该邻域节点代价值之和
                    # 当前节点的累计代价值在上一次循环中被计算过,所以已知
                    # 从当前节点到该邻域节点代价值之和,在基类中被实现,基于栅格地图哈慢炖距离,如果是障碍物则无穷
                new_cost = self.g[s] + self.cost(s, s_n)

                if s_n not in self.g:
                    self.g[s_n] = math.inf

                # 如果以当前节点为父节点,此邻域节点的累计代价值更小,就把原来的剔除Openlist,把现在的加进去,并更新父节点,和累计代价值
                if new_cost < self.g[s_n]:  # conditions for updating Cost
                    self.g[s_n] = new_cost  # 更新累计代价值
                    self.PARENT[s_n] = s    # 更新父节点
                    # 检查剔除原来在openlist中的记录(他给漏掉了,我加上去:)
                    if s_n in self.OPEN:
                        self.OPEN.remove(s_n)

                    # 把邻域节点压入Openlist
                    # dfs, add new node to the front of the openset
                    prior = self.OPEN[0][0]-1 if len(self.OPEN)>0 else 0    # 计算新元素在列表中位置(自动的堆栈结构更好,他这个要自己维护)
                    heapq.heappush(self.OPEN, (prior, s_n)) 

        return self.extract_path(self.PARENT), self.CLOSED

以上代码是我在源代码,根据前一节得到的流程伪代码添加了注释和缺失步骤的版本。
可以看到,和之前说的伪代码流程除了循环中几个顺序不一样(无所谓)外,其他完全一致。

运行结果

在这里插入图片描述

结果分析

可以看到,深度优先在面临选择时,会一条路走到黑:在有多个选择时,先一直往左下方向走;当没有选择时,溜着墙边这一条路走。

他这次表现的不好,主要是默认面临多个选择时,先探索左下角。当然你可以改成默认右上角,但这是由于我们知道了全局地图,但算法不知道,如果写死了,下次出发点在右上角,终点在左下角,还是会出现类似情况。最好的办法是让算法知道目标大概在哪个方向,这就是启发式算法做的工作了。如前所述:

当前节点的相邻节点有好几个,他们应该以什么样的顺序压入堆栈哪?毕竟越晚被压入,越早被取出嘛,压入先后是有不同的。这点随意。比如面对一个岔路,可以顺时针编号,可以逆时针编号,这部分的优化不是深度优先算法的工作。

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

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

相关文章

事件驱动的奇迹:深入理解Netty中的EventLoop

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 事件驱动的奇迹&#xff1a;深入理解Netty中的EventLoop 前言基础概念EventLoop的工作原理Channel与EventLoop的关系定时任务与延时任务EventLoop的生命周期EventLoop中的线程模型性能优化与最佳实践 …

前后端依赖下载上传

在某些情况下&#xff0c;可能需要在没有互联网连接的环境中进行构建或部署。通过提前下载所有依赖&#xff0c;你可以将它们保存在本地&#xff0c;然后在没有网络连接时使用&#xff0c;提高构建或部署的效率。 前端下载依赖 脚本getTzgUrl.js const { readFileSync, writ…

前端架构: 脚手架之包管理工具的案例对比及workspace特性的基本使用

Npm WorkSpace 特性 1 &#xff09;使用或不使用包管理工具的对比 vue-cli 这个脚手架使用 Lerna 管理&#xff0c;它的项目显得非常清晰在 vue-cli 中包含很多 package 点开进去&#xff0c;每一个包都有package.json它里面有很多项目&#xff0c;再没有 Lerna 之前去维护和管…

sql | leecode 1147 |即时事务配送II | sql 优化

sql 有时候还是挺有意思的&#xff0c;有时候不只是crud 下面来看一道sql 题&#xff0c;第一个题解能跑完&#xff0c;但是超时 浮躁的人总是很难看的进去&#xff0c;有时候孤单又在所难免&#xff0c;所以啊 要继续坚持&#xff0c;原来中学老师讲的那句&#xff0c;要耐住寂…

arm服务器上部署kibana

1.首先需要从elasticsearch对应的kibana版本(arm) ​​​​​​Download Kibana Free | Get Started Now | Elastic 注意:选平台时切勿选错,linux aarch64,并选择elasticsearch对应的历史版本 2.可以通过rz命令上传压缩包至 linux 服务器进行解压&#xff0c;存放路径建议和e…

物联网通信协议介绍

为了方便&#xff0c;将物联网通信协议分为两大类&#xff0c;一类是接入协议&#xff0c;一类是通讯协议。接入协议一般负责子网内设备间的组网及通信&#xff1b;通讯协议主要是运行在传统互联网TCP/IP协议之上的设备通讯协议&#xff0c;负责设备通过互联网进行数据交换及通…

数据结构:排序算法+查找算法

一、概念 程序数据结构算法 1.算法的特性和要求 特性&#xff1a; 确定性&#xff08;每次运行相同的输入都是同样的结果&#xff09;、有穷性、输入、输出、可行性 设计要求&#xff1a; 正确性、高效率、低存储、健壮性、可读性 2.时间复杂度 3.常见排序算法的时间复杂…

K8S存储卷与PV,PVC

一、前言 Kubernetes&#xff08;K8s&#xff09;中的存储卷是用于在容器之间共享数据的一种机制。存储卷可以在多个Pod之间共享数据&#xff0c;并且可以保持数据的持久性&#xff0c;即使Pod被重新调度或者删除&#xff0c;数据也不会丢失。 Kubernetes支持多种类型的存储卷…

MyBatis 学习(二)之 第一个 MyBatis 案例

目录 1 配置 MyBatis 方式 1.1 XML 配置文件 1.2 Java 注解配置 1.3. Java API 配置 2 在 MySQL 中创建一张表 3 创建一个基于 Maven 的 JavaWeb 工程 4 编写 User 实体类 5 创建Mybatis全局配置文件 6 编写一个 DAO 或 Mapper 接口 7 编写 SQL 映射配置文件&#xf…

Vuepress的使用

介绍 将markdown静态资源转换成html。 动态资源的转换还有很多&#xff0c;为什么要使用Vuepress&#xff1f; 目录分析 项目配置 详情 具体配置请看文档 插件配置 vuepress-theme-vdoing 主题插件 npm install vuepress-theme-vdoing -D先安装依赖配置主题 使用vuep…

Android 架构MVI、MVVM、MVC、MVP

目录 一、MVC&#xff08;Model-View-Controller&#xff09; 二、 MVP&#xff08;Model-View-Presenter&#xff09; 三. MVVM&#xff08;Model-View-ViewModel&#xff09; 四. MVI&#xff08;Model-View-Intent&#xff09; 五.MVI简单实现 先简单了解一下MVC、MVP和…

5.WEB渗透测试-前置基础知识-常用的dos命令

内容参考于&#xff1a; 易锦网校会员专享课 上一篇内容&#xff1a;4.WEB渗透测试-前置基础知识-快速搭建渗透环境&#xff08;下&#xff09;-CSDN博客 常用的100个CMD指令 1.gpedit.msc—–组策略 2. sndrec32——-录音机 3. Nslookup——-IP地址侦测器 &#xff0c;是一个…

Keepalived 双机热备基础知识

7.1 Keepalived 双机热备基础知识 Keepalived起初是专门针对LVS设计的一款强大的辅助工具&#xff0c;主要用来提供故障切换(Failover) 和健康检查査(Health Checking)功能一一判断LVS 负载调度器、节点服务器的可用性&#xff0c;及时隔离并替 换为新的服务器&#xff0c;当故…

使用腾讯云go sdk 查询对象存储中最新文件

背景&#xff1a; 腾讯云账号下&#xff0c;有很多对象存储COS桶&#xff1a; 我现在想确认某一个对象存储桶的活跃程度&#xff0c;简单的来说。我想知道这个桶里面最后上传的一个文件是什么&#xff0c;以及它的上传时间戳。 本文将介绍如何使用腾讯云对象存储&#xff08;…

iMazing3安全吗?好不好用?值不值得下载

一、安全性 iMazing在设计和开发过程中&#xff0c;始终把用户数据的安全性放在首位。它采用了多种先进的安全技术来确保用户数据在传输、备份和存储过程中的安全。 iMazing3Mac-最新绿色安装包下载如下&#xff1a; https://wm.makeding.com/iclk/?zoneid49816 iMazing3Wi…

森林监测VR虚拟情景再现系统更便利

AI人工智能技术已经逐渐渗透到各个领域&#xff0c;为我们的生活带来了诸多便利。在虚拟仿真教学领域&#xff0c;AI技术的应用也日益丰富&#xff0c;为虚拟情景交互体验带来了前所未有的好处。 提高VR虚拟情景的逼真度 通过深度学习和计算机视觉等技术&#xff0c;AI/VR虚拟现…

java008 - Java方法

1、方法概述 1.1 概念 将独立功能的代码块组织成为一个整体&#xff0c;使其具有特殊功能的代码集。 1.2 注意事项 方法必须先创建才能使用&#xff0c;该过程称为方法的定义方法创建好不能直接运行&#xff0c;需要手动使用才执行&#xff0c;该过程称为方法的调用 2、方…

基于RISC-V架构的通信DSP的设计以及在5G RedCap基带中的应用(五)-基于RISC-V的RedCap DSP在5G基带中的应用

4 基于RISC-V的RedCap DSP在5G基带中的应用 4.1 基带处理器的关键任务和性能需求 基带处理器是移动通信设备中的关键部件&#xff0c;负责处理无线信号&#xff0c;包括信号的接收、发送和处理。在5G通信系统中&#xff0c;基带处理器的关键任务和性能需求包括以下几个方面&a…

【网站项目】424学报稿件管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

面试数据库篇(mysql)- 11主从同步

原理 MySQL主从复制的核心就是二进制日志 二进制日志&#xff08;BINLOG&#xff09;记录了所有的 DDL&#xff08;数据定义语言&#xff09;语句和 DML&#xff08;数据操纵语言&#xff09;语句&#xff0c;但不包括数据查询&#xff08;SELECT、SHOW&#xff09;语句。 复…