【深度优先搜索】——理论基础

深度优先搜索(DFS)理论知识基础

总的来说,深度优先搜索(DFS)是一种用于遍历或搜索图(包括树)的算法,与广度优先搜索不同,它沿着一个方向深入探索直到无法继续再回溯换方向。其搜索过程包括选择起始顶点、深入探索路径和回溯,代码实现有递归和非递归(利用栈)两种方式,递归实现需确定函数参数、设置终止条件和处理当前节点与递归探索相邻节点,非递归则通过栈模拟递归过程。它可用于图的遍历、路径搜索、生成树和森林构建、有向无环图的拓扑排序以及回溯问题等多种场景。

一、定义与概念

深度优先搜索是一种用于遍历或搜索图(包括树,树是一种特殊的图)的算法。它从起始顶点开始,沿着一条路径尽可能深地探索图,直到无法继续(即到达叶子节点或者遇到已经访问过的节点),然后回溯到上一个未完全探索的顶点,继续探索其他路径。

二、与广度优先搜索(BFS)的对比

  • 深度优先搜索(DFS):侧重于沿着一个方向深入探索,就像走迷宫时一直沿着一条通道走,直到走不通了才回头换另一个方向。这种搜索方式在搜索过程中可能会深入到图的较远层次,然后再回溯到较浅层次探索其他分支。
  • 广度优先搜索(BFS):是从起始顶点开始,先访问它的所有邻接顶点,然后再依次访问这些邻接顶点的邻接顶点,一层一层地向外扩展,类似水波从中心向外扩散的过程。

三、搜索过程

  1. 选择起始顶点:首先确定一个起始顶点作为搜索的起点。
  2. 深入探索路径:从起始顶点开始,选择一个与之相邻且未被访问过的顶点,然后递归地对这个新顶点进行相同的操作,即继续选择它的相邻未访问顶点进行探索,一直这样深入下去。
  3. 回溯:当遇到一个顶点,其所有相邻顶点都已被访问过(即走到尽头),就回溯到上一个有未访问邻接顶点的顶点。回溯的过程就是撤销之前的路径选择,然后尝试其他可能的路径。这个过程会不断重复,直到所有可达顶点都被访问过。

四、代码框架

  1. 递归实现
    • 函数定义与参数
      • 通常定义一个函数dfs(参数),参数的选择取决于具体的问题。一般需要传入图的结构表示和当前搜索的节点信息。例如,可以有vector<vector<int>> graph表示图(如果是邻接矩阵或邻接表形式)和int curNode表示当前节点。
      • 还可能需要一些变量来保存搜索结果,如vector<vector<int>> result用于保存符合条件的所有路径,vector<int> path用于保存从起点到当前节点的路径。这些变量可以是全局变量,也可以作为函数参数传递,具体取决于个人习惯和代码结构。

以下是用 Python 实现一个简单的递归 DFS,假设图以邻接表形式表示:

graph = {
    1: [2, 3],
    2: [4],
    3: [5],
    4: [],
    5: []
}

def dfs(node, visited=set()):
    if node in visited:
        return
    visited.add(node)
    print(node)
    for neighbor in graph[node]:
        dfs(neighbor, visited)
  • 终止条件
    • 终止条件用于判断递归何时停止。例如,当找到目标节点、遍历完所有可达节点或者满足某种特定条件时终止。常见的写法是if (终止条件) {存放结果; return;}。终止条件的正确设置很关键,设置不当可能导致死循环或栈溢出等问题。有些情况下,终止条件也可能隐含在递归调用的逻辑中,即不符合继续递归的条件时就不会向下递归。

以下是一个带终止条件的示例,比如找到目标节点 5 就停止:

graph = {
    1: [2, 3],
    2: [4],
    3: [5],
    4: [],
    5: []
}

def dfs(node, target, visited=set()):
    if node == target:
        return True
    if node in visited:
        return False
    visited.add(node)
    for neighbor in graph[node]:
        if dfs(neighbor, target, visited):
            return True
    return False
  • 处理当前节点并递归探索相邻节点
    • 一般通过一个for循环来遍历当前节点所能到达的所有相邻节点。在循环内部,首先处理当前选择的相邻节点(例如,将其加入路径path),然后进行递归调用dfs(图,选择的节点),继续深入探索。在递归调用之后,需要进行回溯操作,撤销对当前节点的处理结果(例如,将其从路径path中移除),以便尝试其他相邻节点。

以下是一个在遍历过程中记录路径的示例:

graph = {
    1: [2, 3],
    2: [4],
    3: [5],
    4: [],
    5: []
}

result = []
path = []

def dfs(node):
    if node in path:
        return
    path.append(node)
    if len(path) > len(result) and node == 5:
        result[:] = path.copy()
    for neighbor in graph[node]:
        dfs(neighbor)
    path.pop()
  1. 非递归实现(使用栈)
    • 除了递归实现外,深度优先搜索也可以使用栈来实现。首先将起始顶点压入栈中,然后进入循环。在每次循环中,弹出栈顶元素,处理该元素(例如,标记为已访问),然后将其未访问的相邻顶点压入栈中。重复这个过程,直到栈为空。这种实现方式模拟了递归调用栈的过程,在某些场景下可以避免递归调用栈溢出的问题。

以下是用栈实现 DFS 的示例:

graph = {
    1: [2, 3],
    2: [4],
    3: [5],
    4: [],
    5: []
}

stack = []
visited = set()

def dfs(start):
    stack.append(start)
    while stack:
        node = stack.pop()
        if node not in visited:
            visited.add(node)
            print(node)
            for neighbor in reversed(graph[node]):
                stack.append(neighbor)

五、应用场景

  1. 图的遍历:用于确定图是否连通,计算图的连通分量数量,遍历图中的所有节点等。

以下是判断图是否连通的示例:

graph = {
    1: [2, 3],
    2: [1, 4],
    3: [1, 5],
    4: [2],
    5: [3]
}

def is_connected(graph):
    visited = set()
    start = list(graph.keys())[0]
    dfs(start, visited)
    return len(visited) == len(graph)

def dfs(node, visited):
    if node in visited:
        return
    visited.add(node)
    for neighbor in graph[node]:
        dfs(neighbor, visited)
  1. 路径搜索:在图中寻找从一个顶点到另一个顶点的所有可能路径,例如在迷宫问题、网络路径搜索等场景中。

以下是在图中寻找从一个顶点到另一个顶点路径的示例:

graph = {
    1: [2, 3],
    2: [4],
    3: [5],
    4: [],
    5: []
}

def find_path(start, end):
    path = []
    visited = set()
    def dfs(node):
        if node == end:
            path.append(node)
            return True
        if node in visited:
            return False
        visited.add(node)
        for neighbor in graph[node]:
            if dfs(neighbor):
                path.append(node)
                return True
        return False
    dfs(start)
    return path[::-1] if path else None
  1. 生成树和森林:可以用于构建图的生成树或森林,特别是在深度优先生成树的生成过程中。

以下是构建深度优先生成树的示例:

graph = {
    1: [2, 3],
    2: [4],
    3: [5],
    4: [],
    5: []
}

tree = {}
visited = set()

def build_dfs_tree(start):
    if start in visited:
        return
    visited.add(start)
    for neighbor in graph[start]:
        if neighbor not in visited:
            if start not in tree:
                tree[start] = []
            tree[start].append(neighbor)
            build_dfs_tree(neighbor)
  1. 拓扑排序(对于有向无环图):结合适当的标记和处理,可以用于有向无环图的拓扑排序。在拓扑排序过程中,通过深度优先搜索可以确定节点之间的先后顺序。

以下是用 DFS 实现拓扑排序的示例:

graph = {
    1: [2, 3],
    2: [4],
    3: [4],
    4: []
}

result = []
visited = set()

def topological_sort(node):
    if node in visited:
        return
    visited.add(node)
    for neighbor in graph[node]:
        topological_sort(neighbor)
    result.append(node)

for node in graph:
    topological_sort(node)

result.reverse()
print(result)
  1. 解决回溯问题:很多回溯算法问题本质上是深度优先搜索的应用,如组合问题、排列问题、子集问题等。在这些问题中,深度优先搜索用于遍历所有可能的解空间,通过回溯来撤销不合适的选择,从而找到满足条件的所有解。

以下是解决组合问题的示例:

nums = [1, 2, 3]
target = 3
result = []

def combination_sum(index, path, target):
    if target == 0:
        result.append(path.copy())
        return
    if target < 0 or index >= len(nums):
        return
    for i in range(index, len(nums)):
        path.append(nums[i])
        combination_sum(i, path, target - nums[i])
        path.pop()

combination_sum(0, [], target)
print(result)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值