基于python实现深度优先遍历搜索(DFS)

2023-05-16

  • 1.1 算法介绍

  • 1.2 实验代码

  • 1.3 实验结果

  • 1.4 实验总结

1.1 算法介绍


深度优先搜索算法(Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点 v 的所在边都己被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。属于盲目搜索。

以上图为例,简述 DFS 的过程。首先从根节点“1”出发,按一定的顺序遍历其子节点,这里我们假设优先遍历左边的。所以,在遍历“1”之后,我们到了节点“2”,此时“2”仍有子节点,所以应继续向下遍历,下一个节点是“3”,然后是“4”。到了“4”之后,没有子节点了,说明我们已经将这一条路遍历完了,接着我们应该回溯,应该回到“4”的父节点,也就是“3”。因为“3”还有一个子节点“5”没有遍历,所以下一个我们应该遍历的是“5”。遍历完“5”之后又发现一条路到头了,再次回溯依然回溯到其父节点“3”,此时“3”的所有子节点都已经遍历完了,因该接着回溯到“3”的父节点“2”,然后检查“2”是否有没有遍历完的子节点。按照这样的规则,完成所有节点的遍历。最终得到的遍历顺序是“1-2-3-4-5-6-7-8-9-10-11-12”

在介绍了 DFS 在遍历树的应用后,我们将其应用于八数码问题的解决。八数码问题也称为九宫问题。在 3×3 的棋盘,摆有八个棋子,每个棋子上标有 1 至 8 的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。所谓问题的一个状态就是棋子在棋盘上的一种摆法。棋子移动后,状态就会发生改变。解八数码问题实际上就是找出从初始状态到达目标状态所经过的一系列中间过渡状态。

上面说的 DFS 遍历的树是已经存在的,我们只需要按照规定的遍历方法就能完成遍历,而对于八数码问题,没有已经存在的路径供我们遍历,需要我们从初始状态向下延伸(也就是上下左右移动)才能构造出类似的树。

以上图为例。在使用 DFS 进行搜索时,每个状态都会按照一定的顺序进行上下左右移动(在上图中是下、左、右、上的顺序),一次移动后会产生一个新的状态,然后以新状态为起点继续按约定的顺序(例如先向下)移动。终止的条件是找到解或者达到深度界限。那么如果按照图中下、左、右、上的顺序搜索后的结果将会是最左边的一条路一直是优先向下移动,如果不能向下则依次会是左、右、上的一种。

1.2 实验代码


import copy
# 棋盘的类,实现移动和扩展状态
class grid:
    def __init__(self, stat):
        self.pre = None
        self.target = [[1, 2, 3], [8, 0, 4], [7, 6, 5]]
        self.stat = stat
        self.find0()
        self.update()
    #更新深度和距离和
    def update(self):
        self.fH()
        self.fG()

    # G是深度,也就是走的步数
    def fG(self):
        if (self.pre != None):
            self.G = self.pre.G + 1
        else:
            self.G = 0

    # H是和目标状态距离之和,可以用来判断是否找到解
    def fH(self):

        self.H = 0
        for i in range(3):
            for j in range(3):
                targetX = self.target[i][j]
                nowP = self.findx(targetX)
                self.H += abs(nowP[0] - i) + abs(nowP[1] - j)

    #以三行三列的形式输出当前状态
    def see(self):
        print("depth:", self.G)
        for i in range(3):
            print(self.stat[i])
        print("-" * 10)

    # 查看找到的解是如何从头移动的
    def seeAns(self):
        ans = []
        ans.append(self)
        p = self.pre
        while (p):
            ans.append(p)
            p = p.pre
        ans.reverse()
        for i in ans:
            i.see()
    #找到数字x的位置
    def findx(self, x):
        for i in range(3):
            if (x in self.stat[i]):
                j = self.stat[i].index(x)
                return [i, j]
    #找到0的位置,也就是空白格的位置
    def find0(self):
        self.zero = self.findx(0)

    #对当前状态进行扩展,也就是上下左右移动,返回的列表中是状态的二维列表,不是对象
    def expand(self):
        i = self.zero[0]
        j = self.zero[1]
        gridList = []
        if (j == 2 or j == 1):
            gridList.append(self.left())
        if (i == 2 or i == 1):
            gridList.append(self.up())
        if (i == 0 or i == 1):
            gridList.append(self.down())
        if (j == 0 or j == 1):
            gridList.append(self.right())
        return gridList

    # deepcopy多维列表的复制,防止指针赋值将原列表改变
    # move只能移动行或列,即row和col必有一个为0
    #对当前状态进行移动的函数
    def move(self, row, col):
        newStat = copy.deepcopy(self.stat)
        tmp = self.stat[self.zero[0] + row][self.zero[1] + col]
        newStat[self.zero[0]][self.zero[1]] = tmp
        newStat[self.zero[0] + row][self.zero[1] + col] = 0
        return newStat

    def up(self):
        return self.move(-1, 0)

    def down(self):
        return self.move(1, 0)

    def left(self):
        return self.move(0, -1)

    def right(self):
        return self.move(0, 1)

# 判断状态g是否在状态集合中,g是对象,gList是对象列表
# 返回的结果是一个列表,第一个值是真假,如果是真则第二个值是g在gList中的位置索引
def isin(g, gList):
    gstat = g.stat
    statList = []
    for i in gList:
        statList.append(i.stat)
    if (gstat in statList):
        res = [True, statList.index(gstat)]
    else:
        res = [False, 0]
    return res

# 计算逆序数之和
def N(nums):
    N=0
    for i in range(len(nums)):
        if(nums[i]!=0):
            for j in range(i):
                if(nums[j]>nums[i]):
                    N+=1
    return N

# 根据逆序数之和判断所给八数码是否可解
def judge(src,target):
    N1=N(src)
    N2=N(target)
    if(N1%2==N2%2):
        return True
    else:
        return False

# 初始状态
startStat = [[2, 8, 3], [1, 0, 4], [7, 6, 5]]
g = grid(startStat)
# 判断所给的八数码受否有解
if(judge(startStat,g.target)!=True):
    print("所给八数码无解,请检查输入")
    exit(1)
# visited储存的是已经扩展过的节点
visited = []
time = 0
# 用递归的方式进行DFS遍历
def DFSUtil(v, visited):
    global time
    #判断是否达到深度界限
    if (v.G > 4):
        return
    time+=1
    #判断是否已经找到解
    if (v.H == 0):
        print("found and times", time, "moves:", v.G)
        v.seeAns()
        exit(1)

    #对当前节点进行扩展
    visited.append(v.stat)
    expandStats = v.expand()
    w = []
    for stat in expandStats:
        tmpG = grid(stat)
        tmpG.pre = v
        tmpG.update()
        if (stat not in visited):
            w.append(tmpG)
    for vadj in w:
        DFSUtil(vadj, visited)
    #visited查重只对一条路,不是全局的,每条路开始时都为空
    #因为如果全局查重,会导致例如某条路在第100层找到的状态,在另一条路是第2层找到也会被当做重复
    #进而导致明明可能会找到解的路被放弃
    visited.pop()

DFSUtil(g, visited)
# 如果找到解程序会在中途退出,走到下面这一步证明没有找到解
print("在当前深度下没有找到解,请尝试增加搜索深度")

1.3 实验结果


以下面这个八数码为例,用 DFS 进行搜索。

将找出的解从初始状态一步一步输出到解状态。

可以看出总共进行了 15 次遍历,在某一条路的第 4 层找到了解。

下面我们来看一看 DFS 的所有 15 次遍历,以此来更深入的理解 DFS 的原理。稍微对代码进行改动,使其输出遍历次数和当前层数。由于结果太长,为了方便展示,下面将以树的形式展示。

上面输出的解就是按照红色路线标注找到的,从遍历次数可以看出 DFS 是一条道走到黑的找法,因为设置的深度界限是 4,所以每一条路最多找到第 4 层。

1.4 实验总结


1、为什么要设置深度界限?

因为理论上我们只需要一条路就可以找到解,只要不停地向下扩展就可以了。而这样做的缺点是会绕远路,也许第一条路找到第 100 层才找到解,但第二条路找两层就能找到解。从 DFS 的原理出发,我们不难看出这一点。还有一个问题是其状态数太多了,在不设置深度界限的情况下经常出现即使程序的栈满了依然没有找到解的情况。所以理论只是理论,在坚持“一条道走到黑”时,很可能因为程序“爆栈”而走到了黑还是没有找到解。

2、如何进行回溯?

在八数码问题中,我们回溯的条件只有一个,就是达到深度界限了。因为在找到解时会退出,找不到时会继续向下扩展。回溯的过程是先回溯到父节点,检查父节点是否还能扩展其他节点,如果能,就扩展新的节点并继续向下搜索,如果不能则递归地继续向上回溯。

3、出现重复状态怎么解决?

不难想出假如按照下、左、右、上这样地顺序进行搜索时,在第三层时就会出现和初始状态相同的情况。因为第二层向一个方向移动,第三层会有一个向反方向移动的状态也就是回到初始状态了。这样不仅增加了运算量,而且没有意义,会出现很多冗余步骤。所以我们应该设置一个查重的表,将已经遍历过的状态存入这个表中,当再次遇到这种情况时我们就跳过。

那么这个查重的表是该对于全局而言呢,还是每条路的查重表是独立的呢?在经过很多测试之后,我发现这个查重表对每条路独立是更好的。因为在一条路上出现的状态所需要的步数和另一条需要的步数不一定相同,也就是说我在第一条路上的第 100 层找到了某个状态,放入了查重表中,但是这个状态可能在另一条路上第 2 层就能找到,或许再下面几层就能找到解了,可是由于被放进了全局查重表中而放弃了这个条路的扩展,也损失了更快找到解的机会。所以一条路一个查重表是好的。

4、由于需要设置深度界限,每条路都会在深度界限处截至, 而如果所给的八数码的最优解大于深度界限,就会出现遍历完所有情况都找不解。而在事先不知道最优解的深度的情况下这个深度界限很难确定,设置大了会增大搜索时间,设置小了会找不到解。这也是 DFS 的一个缺点。

5、DFS 不一定能找到最优解。因为深度界限的原因,找到的解可能在最优解和深度界限之间。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于python实现深度优先遍历搜索(DFS) 的相关文章

  • CAD批量打图精灵功能列表

    功能简介功能细分识别图框识别直线 多段线 二维多段线 三维多段线 面域 视口 代理实体 块参照 外部参照单图模式 xff0c 识别整个图形的边界 xff0c 适用于模型或布局只有一张图的情况多图模式 xff0c 识别矩形或无矩形块边界标准
  • Linux下VirtualBox虚拟机的命令行启动/关闭方法和开机自动启动

    SUN VirtualBox 的命令行启动 关闭方法简介 VirtualBox 详细命令 linux开机自动启动虚拟机系统 当你安装很多套Virtualbox的虚拟机器系统后 xff0c 希望能在开机后自动启动虚拟机器的系统 开启记事本 x
  • NSIS制作安装软件过程

    目录 1 工具介绍 1 1 界面设计用 xff0d xff0d NSIS Dialog Designer 1 2 编辑及向导 xff0d xff0d nisedit2 0 3 1 3 控件信息查看 Au3Info exe 2 脚本的结构 1
  • 欧拉题目收集

    nbsp https www oschina net group kunpeng 赛题6 容器网络可视化 赛题类别 操作系统 nbsp 赛题难度 中 nbsp 赛题描述 容器场景的微服务运维已经在向可视化方式演进 可视化运维中最大的内容是A
  • ASP.NET Core Blazor: 两种IJSRuntime依赖注入的方式

    1 xff09 将 IJSRuntime 抽象注入Razor组件或者页面 razor 中 xff1a public partial class ToolsWidget Inject private IJSRuntime JSRuntime
  • x86 smbus 下挂eeprom不能写问题

    目录 背景 分析 驱动影响 SPD register 接口 只读 修改验证 总结 背景 x86 smbus上下挂一个eeprom xff0c 只能读取 xff0c 不能写入 写入命令采用 xff1a i2cset y f 0 0x50 0
  • ubuntu编译rk3588异常

    问题现象 在ubuntu上编译 rk3588 的kernel时 xff0c 报如下错误 xff1a LZ4C arch arm64 boot Image lz4 Incorrect parameters Usage lz4 arg inpu
  • linux rs485功能增加

    目录 串口驱动层级结构 485配置流程 dts相关 配置注册 初始化 485收发切换 delay after send 目前linux 内核中已经支持了485的实现 xff0c 但由于底层驱动的支持情况 xff0c 导致我们采用不同芯片时需
  • intel I2C的速率配置

    目录 寄存器篇 修改寄存器 intel I2C 驱动结构 lpss pci文件 lpss文件 驱动结构 Synopsys DesignWare I2C BIOS配置修改 ACPI表的查看 I2C速率 寄存器篇 修改速率很简单 xff0c 看
  • spark 例子运行- spark pi

    原计划用cygwin来运行linux的脚本 xff0c 进行测试 但是在实际运行过程中 xff0c 出现java cp x jar class xff0c 总是失败的问题 xff0c 一直没有解决 xff0c 因而 xff0c 直接用win
  • sas控制器驱动之设备管理

    本文以2 6 32 68内核中的mpt2sas为例子 xff0c 介绍了sas驱动的设备管理 1 基本结构 内核中scsi的结构分三层 xff0c 此在网上已有大量资料 xff0c 不再赘述 本文在此基础上增加了mid layer的 tra
  • 主成分分析、聚类分析、因子分析的基本思想及优缺点

    点击打开链接
  • pyqt5之menu和action

    exitAct 61 QAction QIcon 39 exit png 39 39 amp Exit 39 self exitAct setShortcut 39 Ctrl 43 Q 39 exitAct setStatusTip 39
  • 如何获取控件所在的坐标位置

    窗口的坐标体系及接口 获取坐标的接口在Widget类中 xff0c 即控件的坐标信息属于基类的成员 基本的坐标体系如图所示 通过接口打印出 lable 3的坐标值 print self label 3 geometry x print se
  • 【转载+修改】Gnome菜单项与文件打开方式(文件关联)的更改

    转载自 xff1a http hi baidu com red woods blog item 30a5f845a2247f24cffca397 html 转载有修改 xff01 在KDE中我们可以使用系统设置中提供的设置进行文件关联的修改
  • Kali Linux从零基础入门到精通,看完这一篇就够了。

    1 目录 基于Android设备的Kali Linux渗透测试教程基于Android设备的Kali Linux渗透测试教程2Web渗透测试使用kali linuxkali linux中文指南kali linux wireless pente
  • 客户机无法通过mstsc连接到远程主机的解决方法

    客户机无法通过 mstsc 连接到远程主机的解决方法 症状 当通过 mstsc 命令进行连接时 系统提示 客户端无法连接远程计算机 xff1b 连接可能没有启用 xff0c 或者计算机太忙 xff0c 无法接受新连接 也可能网络问题使您无法
  • 五个同步问题的经典模型之一:生产者/消费者问题

    也叫缓存绑定问题 xff08 bounded buffer xff09 xff0c 是一个经典的 多进程同步问题 单生产者和单消费者 有两个进程 xff1a 一组生产者进程和一组消费者进程共享一个初始为空 固定大小为n的缓存 xff08 缓
  • Android 以太网/有线网Ethernet功能开发

    1 功能介绍 以太网的功能是允许设备提供硬件接口通过插入网线的形式访问互联网的功能 接入网线之后 xff0c 设备可以动态的获取 IP xff0c DNS xff0c Gateway等一系列网络属性 xff0c 我们也可以手动配置设备的网络
  • 解决www.54kk.com/baidu劫持浏览器的问题

    endurer 原创 2005 10 27第一版 endurer注 xff1a 为了安全起见 xff0c 下文中的 http 均用 hxxp 代替 刚才一位同事的电脑中的浏览器被恶意网站劫持了 xff0c 请我帮忙处理 同事的电脑使用的是

随机推荐

  • Automatic Login

    sudo vim etc gdm custom confAdd the following lines to the field daemon AutomaticLoginEnable 61 true AutomaticLogin 61 i
  • Java Annotation手册

    版权声明 xff1a 本文可以自由转载 xff0c 转载时请务必以超链接形式标明文章原始出处和作者信息及本声明 作者 cleverpig 作者的Blog http blog matrix org cn page cleverpig 原文 h
  • shell 函数 入参说明

    1 入参个数 2 入参 0 脚本名 1第一个参数 3 64 和 xff1a 34 64 34 34 34 都是所有入参 64 将入参变成一个数组 将入参变成一个字符串 4 数组作为入参 fucn2 arr xff0c 函数内部获取入参数组
  • 程序媛工作几年后的感受!体验?

    黑客技术 点击右侧关注 xff0c 了解黑客的世界 xff01 Java开发进阶 点击右侧关注 xff0c 掌握进阶之路 xff01 Python开发 点击右侧关注 xff0c 探讨技术话题 xff01 作者 xff1a hq nuan 来
  • TIOBE 5月编程语言榜单出炉,C#最受开发者欢迎,C++将冲击Top 3

    x1f447 x1f447 关注后回复 进群 xff0c 拉你进程序员交流群 x1f447 x1f447 TIOBE Index for May 2022 和 4 月相比 xff0c 本月编程语言 Top 10 并没有明显的位置变化 xff
  • crontab 定时任务避免重复执行

    使用crontab设置一个脚本每个一段时间自动执行一次 xff0c 当脚本的执行时间超过crontab设置的时间间隔 xff0c 那个脚本就会在同一时刻同时执行 比如设置crontab每隔五分钟执行一次task sh xff1a span
  • ubuntu 通过 apt-get 安装软件失败时的解决方案

    最近在 vmware上的ubuntu系统下安装 软件时出现安装失败情况 xff0c 在网上搜了一通 xff0c 终于找到了解决方案 遇到的问题和解决方案如下 xff1a 一 apt get install vim二 apt get upda
  • Spring注解处理机制

    前言 众所周知 xff0c spring 从 2 5 版本以后开始支持使用注解代替繁琐的 xml 配置 xff0c 到了 springboot 更是全面拥抱了注解式配置 平时在使用的时候 xff0c 点开一些常见的等注解 xff0c 会发现
  • 解决SpringBoot使用时类找不到问题

    解决方案 第一步 xff1a 勾选这个选项 第二步 xff0c 在pom xml中添加以下代码 lt resources gt lt resource gt lt directory gt src main resources lt dir
  • java设计模式之建造者模式(Builder Pattern)

    目的 xff1a 将产品与产品的创建过程解耦 他是按照相应的步骤来构建产品 下面看一下UML序列图 对于序列图的一个解释 下面来上一个标准代码 Product java package com pxx public class Produc
  • 如何在 Github Pages 搭建库(创建免费域名)来管理和浏览自己的项目

    看了 这篇文章 你能学会 两大技能 如何在 Github Pages 上搭建库来管理自己的项目你能访问你的项目 就像访问域名一样 查看自己做的网页 说明 像我们学前端的朋友 xff0c 好不容易做好一个很炫的网页 xff0c 没法放在网站上
  • vim 快捷键修改

    ubuntu默认的vim确实不好用 xff0c 但它最强大的地方在于可修改的配置文件 xff0c 以及专门为vim所开发的vimscript脚本语言 后者暂时不用学习 xff0c 先来研究一下配置文件 vimrc 是控制 vim 行为的配置
  • Excel合并计算和分类汇总

    一 实现合并计算 合并计算主要实现将几个分开的表格按照需求的函数功能计算到一个表中 xff1a 1 分类合并 将下面三个城市的销售额分类合并到一个表当中 xff08 这里的销售额必须指明地区 xff0c 不然合并计算时会统计求和 xff09
  • 使用连接池方式和多线程方式连接mysql的测试说明

    前面文章讨论了mysql做高可用的配置 xff0c 参考文章链接 xff0c 而本文则是开发项目过程需要用的部分 xff0c 从配置数据库到实用数据库 xff0c 以及再用SQL做BI分析再到SQL优化 xff0c 这些都是全栈工程师的基本
  • Python中的图形绘制-Matplotlib简单动画制作

    Matplotlib 是一个非常广泛的库 xff0c 它也支持图形动画 动画工具以 matplotlib animation 基类为中心 xff0c 它提供了一个框架 xff0c 围绕该框架构建动画功能 主要接口有TimedAnimatio
  • 明面上是个歌手!暗地里是个程序员的明星你只知道许嵩和潘玮柏?

    在5月9日 xff0c 知名演员刘涛在社交平台发文公布 xff1a 已正式入职聚划算成官方优选官了 xff0c 而且还有花名叫刘一刀 xff0c 以后就专职给大家挑好物了 当然 xff0c 刘涛在5 14号已经开始上班了 xff0c 还邀请
  • 教你搭建FTP文件共享服务器

    一 什么是FTP FTP 文件传输协议 xff08 File Transfer Protocol xff0c FTP xff09 是用于在网络上进行文件传输的一套标准协议 xff0c 它工作在 OSI 模型的第七层 xff0c TCP 模型
  • 田忌赛马 - 去哪儿2018校招哈尔滨在线笔试题 - 开发工程师

    时间限制 xff1a C C 43 43 语言 1000MS xff1b 其他语言 3000MS 内存限制 xff1a C C 43 43 语言 65536KB xff1b 其他语言 589824KB 题目描述 xff1a 田忌和齐王赛马
  • Python的多线程爬虫详解

    多线程使用流程 Python 提供了两个支持多线程的模块 xff0c 分别是 thread 和 threading 其中 thread 模块偏底层 xff0c 它相比于 threading 模块功能有限 xff0c 因此推荐大家使用 thr
  • 基于python实现深度优先遍历搜索(DFS)

    1 1 算法介绍 1 2 实验代码 1 3 实验结果 1 4 实验总结 1 1 算法介绍 深度优先搜索算法 xff08 Depth First Search xff0c DFS xff09 是一种用于遍历或搜索树或图的算法 沿着树的深度遍历