算法数据结构——图的遍历之深度优先搜索算法(Depth First Search)

这篇具有很好参考价值的文章主要介绍了算法数据结构——图的遍历之深度优先搜索算法(Depth First Search)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 深度优先搜索简介

深度优先搜索算法(Depth First Search):英文缩写为 DFS。是一种用于搜索树或图的算法。所谓深度优先,就是说每次都尝试向更深的节点走。

深度优先搜索采用了回溯思想,该算法沿着树的深度遍历树的节点,会尽可能深的搜索树的分支。当节点 v 的所在边都己被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。

在深度优先遍历的过程中,我们需要将当前遍历节点 v 的相邻节点暂时存储起来,以便于在回退的时候可以继续访问它们。遍历到的节点顺序符合「后进先出」的特点,这正是「递归」和「堆栈」所遵循的规律,所以深度优先搜索可以通过「递归」或者「堆栈」来实现。

2. 深度优先搜索过程演示

接下来我们以一个无向图为例,演示一下深度优先搜索的过程。

我们用邻接字典的方式存储无向图结构,对应结构如下:

# 定义无向图结构
graph = {
    "A": ["B", "C"],
    "B": ["A", "C", "D"],
    "C": ["A", "B", "D", "E"],
    "D": ["B", "C", "E", "F"],
    "E": ["C", "D"],
    "F": ["D"]
}

该无向图对应的邻接字典表示:无向图中有 ABCDEF6 个节点,其中与 A 节点相连的有 BC 两个节点,与 B 节点相连的有 ACD 三个节点,等等。

该无向图的结构如图左所示,其深度优先搜索的遍历路径如图右所示。

深度优先搜索,算法面试精选汇编,算法,深度优先,数据结构,图

其深度优先搜索的遍历过程如下动态图所示。

深度优先搜索,算法面试精选汇编,算法,深度优先,数据结构,图

3. 基于递归实现的深度优先搜索

3.1 基于递归实现的深度优先搜索实现步骤

  1. 定义 graph 为存储无向图的字典变量,visited 为标记访问节点的 set 集合变量。start 为当前遍历边的开始节点。def dfs_recursive(graph, start, visited): 为递归实现的深度优先搜索方法。

  2. start 标记为已访问,即将 start 节点放入 visited 中(visited.add(start))。

  3. 访问节点 start,并对节点进行相关操作(看具体题目要求)。

  4. 遍历与节点 start 相连并构成边的节点 end

    1. 如果 end 没有被访问过,则从 end 节点调用递归实现的深度优先搜索方法,即 dfs_recursive(graph, end, visited)

3.2 基于递归实现的深度优先搜索实现代码

def dfs_recursive(graph, start, visited):
    # 标记节点
    visited.add(start)
    # 访问节点
    print(start)
​
    for end in graph[start]:
        if end not in visited:
            # 深度优先遍历节点
            dfs_recursive(graph, end, visited)

4. 基于堆栈实现的深度优先搜索

4.1 基于堆栈实现的深度优先搜索实现步骤

  1. start 为开始节点。定义 visited 为标记访问节点的 set 集合变量。定义 stack 用于存放临时节点的栈结构。

  2. 首先访问起始节点,并对节点进行相关操作(看具体题目要求)。

  3. 然后将起始节点放入栈中,并标记访问。即 visited = set(start)stack = [start]

  4. 如果栈不为空,取 stack 栈顶元素 node_u

  5. 遍历与节点 node_u 相连并构成边的节点 node_v

    • 如果 node_v 没有被访问过,则:

      • 访问节点 node_v,并对节点进行相关操作(看具体题目要求)。

      • node_v 节点放入栈中,并标记访问,即 stack.append(node_v)visited.add(node_v)

      • 跳出遍历 node_v 的循环。

    • 继续遍历 node_v

  6. 如果 node_u 相邻的节点都访问结束了,从栈顶弹出 node_u,即 stack.pop()

  7. 重复步骤 4 ~ 6,直到 stack 为空。

4.2 基于堆栈实现的深度优先搜索实现代码

def dfs_stack(graph, start):
    print(start)                        # 访问节点 start
    visited = set(start)                # 使用 visited 标记访问过的节点,先标记 start
    stack = [start]                     # 创建一个栈,并将 start 加入栈中
    
    while stack:
        node_u = stack[-1]              # 取栈顶元素
        
        i = 0
        while i < len(graph[node_u]):   # 遍历栈顶元素,遇到未访问节点,访问节点并跳出。
            node_v = graph[node_u][i]
            
            if node_v not in visited:   # node_v 未访问过
                print(node_v)           # 访问节点 node_v
                stack.append(node_v)    # 将 node_v 加入栈中
                visited.add(node_v)     # 标记为访问过 node_v
                break
            i += 1
        
        if i == len(graph[node_u]):    # node_u 相邻的节点都访问结束了,弹出 node_u
            stack.pop()

5. 深度优先搜索应用

5.1 岛屿数量

5.1.1 题目链接

  • 200. 岛屿数量 - 力扣(LeetCode)

5.1.2 题目大意

描述:给定一个由字符 '1'(陆地)和字符 '0'(水)组成的的二维网格 grid

要求:计算网格中岛屿的数量。

说明

  • 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

  • 此外,你可以假设该网格的四条边均被水包围。

  • $m == grid.length$。

  • $n == grid[i].length$。

  • $1 \le m, n \le 300$。

  • grid[i][j] 的值为 '0''1'

示例

输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1
​
​
输入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3

5.1.3 解题思路

如果把上下左右相邻的字符 '1' 看做是 1 个连通块,这道题的目的就是求解一共有多少个连通块。

使用深度优先搜索或者广度优先搜索都可以。

思路 1:深度优先搜索

  1. 遍历 grid

  2. 对于每一个字符为 '1' 的元素,遍历其上下左右四个方向,并将该字符置为 0,保证下次不会被重复遍历。

  3. 如果超出边界,则返回 0

  4. 对于 (i, j) 位置的元素来说,递归遍历的位置就是 (i - 1, j)(i, j - 1)(i + 1, j)(i, j + 1) 四个方向。每次遍历到底,统计数记录一次。

  5. 最终统计出深度优先搜索的次数就是我们要求的岛屿数量。

思路 1:代码

class Solution:
    def dfs(self, grid, i, j):
        n = len(grid)
        m = len(grid[0])
        if i < 0 or i >= n or j < 0 or j >= m or grid[i][j] == '0':
            return 0
        grid[i][j] = '0'
        self.dfs(grid, i + 1, j)
        self.dfs(grid, i, j + 1)
        self.dfs(grid, i - 1, j)
        self.dfs(grid, i, j - 1)
​
    def numIslands(self, grid: List[List[str]]) -> int:
        count = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':
                    self.dfs(grid, i, j)
                    count += 1
        return count

思路 1:复杂度分析

  • 时间复杂度:$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。

  • 空间复杂度:$O(m \times n)$。

5.2 克隆图

5.2.1 题目链接

  • 133. 克隆图 - 力扣(LeetCode)

5.2.2 题目大意

描述:以每个节点的邻接列表形式(二维列表)给定一个无向连通图,其中 adjList[i] 表示值为 i + 1的节点的邻接列表,adjList[i][j] 表示值为 i + 1 的节点与值为 adjList[i][j] 的节点有一条边。

要求:返回该图的深拷贝。

说明

  • 节点数不超过 100

  • 每个节点值 $Node.val$ 都是唯一的,$1 \le Node.val \le 100$。

  • 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。

  • 由于图是无向的,如果节点 p 是节点 q 的邻居,那么节点 q 也必须是节点 p 的邻居。

  • 图是连通图,你可以从给定节点访问到所有节点。

示例

深度优先搜索,算法面试精选汇编,算法,深度优先,数据结构,图

输入:adjList = [[2,4],[1,3],[2,4],[1,3]]
输出:[[2,4],[1,3],[2,4],[1,3]]
解释:
图中有 4 个节点。
节点 1 的值是 1,它有两个邻居:节点 2 和 4 。
节点 2 的值是 2,它有两个邻居:节点 1 和 3 。
节点 3 的值是 3,它有两个邻居:节点 2 和 4 。
节点 4 的值是 4,它有两个邻居:节点 1 和 3 。

深度优先搜索,算法面试精选汇编,算法,深度优先,数据结构,图

输入:adjList = [[2],[1]]
输出:[[2],[1]]

5.2.3 解题思路

所谓深拷贝,就是构建一张与原图结构、值均一样的图,但是所用的节点不再是原图节点的引用,即每个节点都要新建。

可以用深度优先搜索或者广度优先搜索来做。

思路 1:深度优先搜索

  1. 使用哈希表 visitedDict 来存储原图中被访问过的节点和克隆图中对应节点,键值对为 原图被访问过的节点:克隆图中对应节点。

  2. 从给定节点开始,以深度优先搜索的方式遍历原图。

    1. 如果当前节点被访问过,则返回隆图中对应节点。

    2. 如果当前节点没有被访问过,则创建一个新的节点,并保存在哈希表中。

    3. 遍历当前节点的邻接节点列表,递归调用当前节点的邻接节点,并将其放入克隆图中对应节点。

  3. 递归结束,返回克隆节点。

思路 1:代码

class Solution:
    def cloneGraph(self, node: 'Node') -> 'Node':
        if not node:
            return node
        visitedDict = dict()
​
        def dfs(node: 'Node') -> 'Node':
            if node in visitedDict:
                return visitedDict[node]
​
            clone_node = Node(node.val, [])
            visitedDict[node] = clone_node
            for neighbor in node.neighbors:
                clone_node.neighbors.append(dfs(neighbor))
            return clone_node
​
        return dfs(node)

思路 1:复杂度分析

  • 时间复杂度:$O(n)$。其中 $n$ 为图中节点数量。

  • 空间复杂度:$O(n)$。文章来源地址https://www.toymoban.com/news/detail-707034.html

到了这里,关于算法数据结构——图的遍历之深度优先搜索算法(Depth First Search)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 大话数据结构-图的深度优先遍历和广度优先遍历

      图的遍历分为深度优先遍历和广度优先遍历两种。   深度优先遍历(Depth First Search),也称为深度优先搜索,简称DFS,深度优先遍历,是指从某一个顶点开始,按照一定的规则,访问并记录下一个未访问顶点。对于非连通图,则是按连通分量,采用同一规则进行深度优

    2024年02月04日
    浏览(49)
  • (超详细)C++图的深度优先遍历、广度优先遍历(数据结构)

            根据下图,编写代码实现图的深度优先遍历和广度优先遍历。          按照英文字母顺序,以邻接表为存储结构,实现图的深度优先和广度优先遍历。遍历的顺序从顶点a开始。 以用户指定的结点为起点,分别输出每种遍历下的结点访问序列。   (1)从顶点a,

    2024年02月08日
    浏览(50)
  • 【数据结构】图的遍历:广度优先(BFS),深度优先(DFS)

    目录 1、广度优先(BFS) 算法思想  广度优先生成树  知识树  代码实现  2、深度优先(DFS) 算法思想  深度优先生成树 知识树  代码实现           图的广度优先遍历(BFS)是一种遍历图的算法,其思想是从起始顶点开始遍历图,先访问起始顶点的所有直接邻居,然

    2024年02月04日
    浏览(49)
  • 【数据结构】图的创建和深度(DFS)广度(BFS)优先遍历

    图是由顶点的有穷非空集合和顶点之间边的集合组成,通过表示为G(V,E),其中,G标示一个图, V是图G中 顶点的集合 , E是图G中 边的集合 。 图分为 无向图 和 有向图 无向图: 若顶点Vi到Vj之间的边没有方向,则称这条边为无向边(Edge),用序偶对(Vi,Vj)表示。 有向图: 若从

    2024年02月05日
    浏览(45)
  • 【数据结构】3个例题带你理解图的遍历:深度优先搜索

    1、定义 2、性能分析 3)算法实现 💟作者简介:大家好呀!我是 路遥叶子 ,大家可以叫我 叶子 哦!❣️     📝个人主页:【叶子博客】 🏆博主信息: 四季轮换叶,一路招摇胜! 🐋希望大家多多支持😘一起进步呀#

    2024年02月08日
    浏览(28)
  • 数据结构实验6 :图的存储与遍历(邻接矩阵的深度优先遍历DFS和邻接表的广度优先遍历BFS)

    利用邻接矩阵存储无向图,并从0号顶点开始进行深度优先遍历。 输入第一行是两个整数n1 n2,其中n1表示顶点数(则顶点编号为0至n1-1),n2表示图中的边数。 之后有n2行输入,每行输入表示一条边,格式是“顶点1 顶点2”,把边插入图中。 例如: 4 4 0 1 1 3 0 3 0 2 先输出存储

    2024年02月09日
    浏览(53)
  • 【数据结构与算法】图的搜索——广度优先遍历、最小生成树

    邻接链表: 用字典实现.有向图的邻接链表的总长度等于图的边个数;无向图的邻接链表的总长度等于图的边个数的2倍. 邻接矩阵:用于最短路径算法. 该数据结构维护一个不相交动态集的集合,每个集合有一个代表,不关心谁做代表。 支持三种操作: MAKE_SET(x) FIND_SET(x) UNION(x,y

    2024年02月19日
    浏览(36)
  • 【数据结构与算法】图遍历算法 ( 深度优先搜索 DFS | 深度优先搜索和广度优先搜索 | 深度优先搜索基本思想 | 深度优先搜索算法步骤 | 深度优先搜索理论示例 )

    图 的 遍历 就是 对 图 中的 结点 进行遍历 , 遍历 结点 有如下两种策略 : 深度优先搜索 DFS 广度优先搜索 BFS \\\" 深度优先搜索 \\\" 英文名称是 Depth First Search , 简称 DFS ; DFS 基本思想 : 访问第一个邻接结点 : 从 起始点 出发 , 该 起始点 可能有 若干 邻接结点 , 访问 第一个 邻接结点

    2024年02月02日
    浏览(34)
  • 【数据结构】图的广度优先遍历

    广度优先遍历,类似于树的层次遍历,又是熟悉的队列实现。首先将第一个顶点添加到队列中,然后讲该顶点的所有邻接顶点都加入队列中,再将该顶点输出。如此重复直到遍历完整个图。 Q:队列,用于存放顶点。 front,rear:队头和队尾指针,用于入队和出队。 p:工作指针,用

    2024年02月05日
    浏览(35)
  • 数据结构--5.3图的遍历(广度优先遍历)

    广度优先遍历:         广度优先遍历(BreadthFirstSearch),又称为广度优先搜索,简称BFS。 要实现对图的广度遍历,我们可以利用队列来实现。  (参考队列)(上述为结构)

    2024年02月10日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包