【数据结构与算法】———深度优先:“死磕 + 回头” 的艺术

深度优先搜索:从 "一条道走到黑" 到搞定算法题

刷抖音时总刷到 "当代年轻人现状:选了一条路就硬走,撞了南墙才回头"?这不就是深度优先搜索(DFS)的精髓嘛!DFS 这算法跟咱年轻人的 "犟脾气" 如出一辙 ------ 遇到岔路口先闷头往前走,走不通了再退回去换条路,主打一个 "不撞南墙不回头,撞了南墙就回溯"。今天咱就用 Python 代码拆解这算法,看看它咋帮咱搞定力扣难题和面试考点。

啥是 DFS?说白了就是 "死磕到底"

DFS 的核心逻辑特简单:从起点出发,先把一条路走到头,实在没路了就退回到上一个路口,换条路继续死磕。就像玩密室逃脱时,你总不能刚看到个门就掉头吧?不得先进去瞅瞅有没有钥匙嘛!

实现 DFS 有两种姿势:

  • 递归版:自带 "后悔药"(函数调用栈帮你记着回头的路)
  • 栈版:手动记路(适合怕递归太深 "爆栈" 的场景)

咱先看个二叉树遍历的例子,这是 DFS 的经典秀场。

二叉树遍历:DFS 的 "开胃小菜"

二叉树的前序遍历(根→左→右)就是典型的 DFS 操作。比如给你棵树:

markdown 复制代码
    1
     \
      2
     /
    3

按 DFS 的思路,先啃根节点 1,再一头扎进右子树,摸到 2 之后又钻进左子树找到 3,完美符合 "一条道走到黑" 的原则。

递归实现:简单到离谱

python 复制代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
def dfs_tree(root):
    result = []
    def traverse(node):
        if not node:
            return
        # 先拿根节点的value
        result.append(node.val)
        # 左子树死磕到底
        traverse(node.left)
        # 右子树死磕到底
        traverse(node.right)
    traverse(root)
    return result
# 测试一下
root = TreeNode(1, right=TreeNode(2, left=TreeNode(3)))
print(dfs_tree(root))  # 输出 [1,2,3],跟预期一模一样

这代码简洁得像抖音神曲,几行就搞定。但如果树太深(比如几万层),递归可能会 "栈溢出",这时候就得用栈手动模拟。

栈实现:手动记路不翻车

ini 复制代码
def dfs_stack(root):
    if not root:
        return []
    stack = [root]
    result = []
    while stack:
        node = stack.pop()  # 弹出栈顶元素
        result.append(node.val)
        # 右子树先入栈,因为栈是"后进先出",保证左子树先被访问
        if node.right:
            stack.append(node.right)
        if node.left:
            stack.append(node.left)
    return result
print(dfs_stack(root))  # 同样输出 [1,2,3]

用栈的时候得注意入栈顺序,右子树先塞进去,才能让左子树先被 "啃" 到,这点跟递归版保持一致。

力扣热门题:DFS 实战秀

光说不练假把式,咱拿两道面试高频题练练手,看看 DFS 咋大显神通。

1. 岛屿数量(LeetCode 200):DFS 的 "扫雷" 时刻

这题就像玩扫雷,碰到一个 "1"(陆地),就得把周围所有连在一起的 "1" 都标记成 "0"(水),每扫完一片就是一个岛屿。

python 复制代码
def numIslands(grid):
    if not grid:
        return 0
    rows, cols = len(grid), len(grid[0])
    count = 0
    
    def dfs(i, j):
        # 越界或者不是陆地,直接撤退
        if i < 0 or i >= rows or j < 0 or j >= cols or grid[i][j] != '1':
            return
        # 标记为已访问(改成水)
        grid[i][j] = '0'
        # 上下左右四个方向继续扫
        dfs(i-1, j)  # 上
        dfs(i+1, j)  # 下
        dfs(i, j-1)  # 左
        dfs(i, j+1)  # 右
    
    for i in range(rows):
        for j in range(cols):
            if grid[i][j] == '1':
                count += 1
                dfs(i, j)  # 把整个岛屿都"淹"掉
    return count
# 测试用例
grid = [
    ["1","1","0","0","0"],
    ["1","1","0","0","0"],
    ["0","0","1","0","0"],
    ["0","0","0","1","1"]
]
print(numIslands(grid))  # 输出3,完美!

这题的关键是 "标记已访问",不然会重复计算。就像抖音上的 "打卡",去过的地方得打个勾,免得下次又跑过去。

2. 全排列(LeetCode 46):DFS 的 "排列组合" 小课堂

给一串数字,返回所有可能的排列。比如 1,2,3,得返回 6 种组合。这题用 DFS + 回溯简直绝配,就像穿衣服 ------ 先穿 1,再试 2,最后套 3;不行就脱了 3 换 2,以此类推。

python 复制代码
def permute(nums):
    result = []
    n = len(nums)
    
    def dfs(path, used):
        # 路径长度够了,就加入结果
        if len(path) == n:
            result.append(path.copy())
            return
        for i in range(n):
            if not used[i]:
                # 选当前数字
                used[i] = True
                path.append(nums[i])
                # 继续选下一个
                dfs(path, used)
                # 回溯:把选的数字放回去
                path.pop()
                used[i] = False
    
    dfs([], [False]*n)
    return result
print(permute([1,2,3]))  # 输出所有6种排列,一目了然

这里的 "回溯" 就像抖音上的 "后悔药" 特效,选错了立马撤回重来,特方便。

面试常问:DFS 的 "优缺点" 和 "应用场景"

面试官最爱问 "DFS 适合啥场景?""它跟 BFS 有啥区别?",咱得答得像刷抖音刷到的知识点一样顺口:

  • 适合的场景
    • 迷宫问题(找出口、走迷宫)
    • 排列组合(全排列、子集问题)
    • 连通性问题(岛屿数量、朋友圈)
    • 拓扑排序(课程表问题)
  • 优点:代码简单(递归版几行搞定),内存占用比 BFS 少(不用记一整层的节点)。
  • 缺点:可能绕远路(找不到最短路径),递归太深容易 "爆栈"(比如处理 10 万层的树)。

记住:求最短路径选 BFS,求所有可能路径选 DFS,就像抖音上的 "选对赛道很重要"。

总结:DFS 就是 "死磕 + 回头" 的艺术

DFS 这算法说难也难,说简单也简单。核心就是 "一条道走到黑,走不通就回头",跟咱生活中试错的过程一模一样。多练几道力扣题(比如子集、电话号码的字母组合),你就会发现:哦,原来 DFS 就是这么回事儿,之前真是想复杂了!

最后送句抖音热评:"递归一时爽,一直递归一直爽,直到栈溢出 ------ 那就换栈实现呗!" 掌握 DFS,算法题从此少掉一把头发~

相关推荐
固定资产管理系统软件7 小时前
商务局RFID固定资产管理系统的应用价值与落地要点解析
大数据·python
TheBestRucy8 小时前
Python 九阳神功:从零筑基到线程飞升
服务器·开发语言·网络·人工智能·python
叩码以求索8 小时前
浅谈:前序遍历反转法求解N叉树的后序遍历
算法
lancyu8 小时前
关于多轮对话机器人的上下文Token优化和解决方案
人工智能·python·深度学习·机器学习·chatgpt·机器人·prompt
草莓橙子碗9 小时前
Claude 使用指南
开发语言·python
工具分享9 小时前
陪跑跟品爆单AI选品助手,高效跟品会赔本吗?
人工智能·python
COOLMO研究AI9 小时前
Python 如何实现 AI API 的提示词(Prompt)版本管理与热更新
人工智能·python·prompt
老郑聊AI业财智造9 小时前
Transformer 技术架构与源码分析
人工智能·python·深度学习·语言模型·架构·transformer·软件工程
北风toto9 小时前
中缀、前缀、后缀表达式
算法·软件设计师
程序员三藏9 小时前
自动化测试用例编写详解
自动化测试·软件测试·python·功能测试·测试工具·职场和发展·测试用例