[算法系列]搞懂DFS(1)——经典例题(数独游戏, 部分和, 水洼数目)图文详解

本文通过数独游戏、部分和及水洼数目三个经典例题,详细讲解了DFS(深度优先搜索)的思路与解题步骤,结合递归设计方法,深入探讨了DFS在算法问题中的应用。
摘要由CSDN通过智能技术生成

本文是递归系列的第四篇文章.

在前面的递归相关的设计思路, 例题介绍的基础上, 本文通过图文并茂的方式详细介绍三道比较经典的dfs题的思考方向和解题步骤, 以此介绍dfs的一般思路,以及加深对递归设计的认识. 觉得不错就小赞一下啦~

1. 数独游戏

数独游戏大家一定都玩过吧: 简单来说就如下的格子中, 填上剩余空白处的数字1-9,使得每行每列以及所在的小九宫格的所有数字均不同.

在这里插入图片描述
我以前并没有玩过数独…也不知道这类题有什么奇技淫巧没, 下面介绍下大概是普通人能够想到思路 :(1a代表左上第一个格子)

  • 根据规则,1a不能填3,4,5,7,8. 为了体现规律性, 我们对剩下的可选数字排序, 每次选都从小开始往上挑 — 选1a为1

  • 接下来是1b, 选1b为2,符合; 接下来1e为4; 1f6; 1g为8;1h为7;目前有如下结果:

在这里插入图片描述

  • 现在1i只剩下9可选了,由于7i已经是9,所以该填法出错了… 然后我们拿着小橡皮, 将1h上的7 “擦掉”, 填上剩下的一种可能–9,现在1i只能填7了, 检查一下,完美. 接下来继续是第二行…第三行…

在这里插入图片描述

好了, 现在引出今天的主题: dfs(深度优先搜索), 以及 回溯

dfs通俗来讲, 就像小时走大迷宫一样. 遇到岔路口后, 选择其中一条 ,不撞南墙不回头不回头. 遇到尽头后, 回溯 到之前的岔路的位置, 然后选择另一条路径. 如果所有的岔路都试完了均是死路的话, 就说明我正处的这个岔路所在的路径是走错了, 因而就得再一次 回溯 到前一个岔路口, 选择另一个岔路…

抽理一下:

在上述走迷宫中, 站在每一个岔路口时,我们都定义是一种 状态 Si, 当我们(通常按照一定顺序) 选择 某一条路径时 ,:

  • 要么是死路, 这时我们需要 回到刚刚的状态 Si(回溯), 选择另一条路径
  • 要么达到下一个路口, 就进入了下一个状态 Si+1

而对这个 下一个状态 Si+1 , 我们使用和上述同样的做法 . 这就是DFS的精髓了.

下面继续通过数独题目介绍dfs及其解法思路

输入数独游戏题目, 格式为 9 * 9 的二维数组 ,0 表示未知,其他数字已知
每个零处需填入数字1-9,使得每行 每列 以及 所在的小九宫格 的所有数字均不同.
输入:

005 300 000
800 000 020
070 010 500
400 005 300
010 070 006
003 200 080
060 500 009
004 000 030
000 009 700


下面给出 dfs 思路,

  • 定义状态 : 坐标为(x,y), 且需要填数字的格子
  • 状态转移: 当前位置填好后, 填它右边最近那个需要填数字的格子, 若是最后一个则提行
  • 选择路径顺序(这里是选择数字顺序): 从1~9中选出满足条件的最小的那个, 回溯后, 选倒数第二小的, 依次类推

而通过走迷宫的方法可以看出, 解决Si和解决Si+1的方法相同, 这其实更是个递归问题:

  • 找出口: 当遍历到 x = 9时 , 则说明下标为0-8的9行全部填完, 即可退出.
  • 找重复: 对每一个状态,判断填入数字的合法规则, 以及选择填入数字的顺序是相同的
  • 找变化: 很显然, 每个状态的数组的完成度是不同的, 同时待填入格子的下标也是不同的 .

上述三部曲也是前面提到过的递归设计方法,详情链接:
搞懂递归, 看这篇就够了 !! 递归设计思路 + 经典例题层层递进

好了, 伪代码也能上了:

dfs(table, x, y):  # table为当前的数组, x,y为当前状态所需填的格子坐标
# 出口条件
if x == 9:
    exit(0)

if table[x][y] == 0:  # 如果为0, 表示需要填
    for i in range(1, 10):  # 选1-9之间的数字放进去, 从小的开始选
        flag = checked(table, x, y, i)  # 判断是否符合同行同列等
        if flag:  # 如果满足就填入 i
            table[x][y] = i
            # 然后转移到下一个状态
            dfs(table, x + (y + 1) / 9, (y + 1) % 9)
    table[x][y] = 0  # for循环完了, 都不满足,  先将此处恢复成0
# 该层代码 完成, 返回上一层调用 ==> 回溯

else:
# 选择下一个需要处理的位置
dfs(table, x + (y + 1) / 9, (y + 1) % 9)



刚开始学的时候可能对其中核心部分还是有些疑惑:

for i in range (1,10):# 选1-9之间的数字放进去, 从小的开始选
    flag = checked(table,x,y,i) # 判断是否符合同行同列等
    if flag:      # 如果满足就
        table[x][y] = i # 填入 i
        dfs(table, x + (y+1) /9 , (y+1) % 9) #递归调用 ,转移到下一个状态
table[x][y] = 0  #for循环完了, 都不满足, 先将此处恢复成0
# 函数执行完成, 返回上一层调用处 ==> 回溯


从1-9中选了一个数字, 如果满足, 则填上此数, 同时考察下一个位置 ;

如果不满足, 即flag = false: 就会对1-9中的下一个数进行考察, 如果全都不满足flag = true, 则说明无路可走(死路), 此时需要先将该处恢复成0 , 然后紧接着函数执行完成, 也就返回到上一次调用的地方, 依然在for循环中, 会重新选择上次的数字(比如:上次选了i=5满足, 递归调用后发现下一个位置是怎么填都是死路, 那么回溯后 i 就会继续遍历得到下个满足的数字)

在这里插入图片描述

代码如下:


def shudu(table, x, y):
    if x == 9 :     #此时表明x已经将0-8的9行全部搞定了
        print_matrix(table)
        exit(0)     #找到一个解即可退出

    if table[x][y] == 0:
        # 选1-9之间的数字放进去
        for i in range(1, 10):
            flag = checked(table, x, y, i)
            if flag:
                table[x]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值