• 【Python搜索算法】深度优先搜索(DFS)算法原理详解与应用,示例+代码


    目录

    1 基本原理

    2 DFS算法流程

    3 时间复杂度

    4 空间复杂度

    5 DFS算法应用案例:

    5.1 解决路径查找问题 

    5.2 解决图的连通性问题

    5.3  拓扑排序

    5.4  在树结构中进行深度遍历


    深度优先搜索(DFS)是一种重要的图遍历算法,用于探索图中的节点和边。

    1 基本原理

    • DFS 是一种递归或栈(堆栈)数据结构的算法,用于图的遍历。
    • 从一个起始节点开始,尽可能深入图的分支,直到无法继续深入,然后回溯并探索其他分支。
    • 通过标记已访问的节点来避免重复访问。

    2 DFS算法流程

    1. 创建一个空的栈(Stack)数据结构,用于存储待访问的节点。

    2. 从起始节点开始,将其标记为已访问并入栈。

    3. 重复以下步骤,直到栈为空: a. 出栈一个节点,并标记为已访问。 b. 检查该节点的所有未被访问的邻居节点。 c. 对于每个未访问的邻居节点,将其标记为已访问并入栈。

    4. 如果无法再继续,即没有未访问的邻居节点,返回上一个节点并继续。

    5. 重复步骤2-4,直到遍历整个图。

    3 时间复杂度

    • 在最坏情况下,DFS的时间复杂度可以是O(V + E),其中V是节点数,E是边数。
    • 由于DFS可能访问整个图,因此在稠密图中可能效率较低。

    4 空间复杂度

    • 空间复杂度取决于递归深度或堆栈的大小,通常为O(V) 

    5 DFS算法应用案例:

    5.1 解决路径查找问题 

            一个常见的应用案例是查找从起始节点到目标节点的路径。例如,在以下示例图中,我们要查找从节点A到节点G的路径。

    下面是一个简单的Python代码示例,用于执行DFS算法,找到从节点A到节点G的路径。

    1. # 定义示例图
    2. GRAPH = {
    3. 'A': ['B', 'C'],
    4. 'B': ['D', 'E'],
    5. 'C': ['F'],
    6. 'D': [],
    7. 'E': ['F'],
    8. 'F': ['G'],
    9. 'G': []
    10. }
    11. # 定义DFS算法,查找从起始节点到目标节点的路径
    12. def dfs(graph, start, end, path=[]):
    13. # 将当前节点添加到路径中
    14. path = path + [start]
    15. # 如果当前节点等于目标节点,返回找到的路径
    16. if start == end:
    17. return path
    18. # 如果当前节点不在图中,返回None
    19. if start not in graph:
    20. return None
    21. # 遍历当前节点的邻居节点
    22. for node in graph[start]:
    23. # 如果邻居节点不在已访问的路径中,继续DFS
    24. if node not in path:
    25. new_path = dfs(graph, node, end, path)
    26. # 如果找到路径,返回该路径
    27. if new_path:
    28. return new_path
    29. # 如果无法找到路径,返回None
    30. return None
    31. # 调用DFS算法查找从A到G的路径
    32. path = dfs(GRAPH, 'A', 'G')
    33. if path:
    34. print("Path from A to G:", path)
    35. else:
    36. print("No path found.")

    输出:

    5.2 解决图的连通性问题:查找下图中的连通组件

    1. import networkx as nx
    2. import matplotlib.pyplot as plt
    3. # 定义一个有向图的邻接列表表示
    4. graph = {
    5. 'A': ['B', 'C'],
    6. 'B': ['A'],
    7. 'C': ['A'],
    8. 'D': ['E'],
    9. 'E': ['D'],
    10. 'F': [],
    11. }
    12. def find_connected_components(graph):
    13. def dfs(node, component):
    14. visited.add(node)
    15. component.append(node)
    16. for neighbor in graph.get(node, []):
    17. if neighbor not in visited:
    18. dfs(neighbor, component)
    19. visited = set()
    20. connected_components = []
    21. for node in graph:
    22. if node not in visited:
    23. component = []
    24. dfs(node, component)
    25. connected_components.append(component)
    26. return connected_components
    27. # 查找连通组件
    28. components = find_connected_components(graph)
    29. # 打印连通组件
    30. for i, component in enumerate(components, start=1):
    31. print(f"Connected Component {i}: {component}")
    32. # 创建有向图
    33. G = nx.DiGraph(graph)
    34. # 绘制图形
    35. pos = nx.spring_layout(G)
    36. nx.draw(G, pos, with_labels=True, node_size=500, node_color='lightblue', font_size=10, font_color='black', font_weight='bold')
    37. # 添加边的标签
    38. labels = {}
    39. for node in G.nodes():
    40. labels[node] = node
    41. edge_labels = {(u, v): v for u, v in G.edges()}
    42. nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=8)
    43. plt.show()

    输出:

              示例创建 find_connected_components 函数,用于查找图中的连通组件。它使用深度优先搜索(DFS)来遍历图,找到连通组件,并将它们添加到 connected_components 列表中。解析如下:

    1. find_connected_components(graph) 函数接受一个有向图的邻接列表表示为输入,并返回图中的连通组件。

    2. 内部嵌套的 dfs(node, component) 函数是深度优先搜索函数。它采用两个参数:

      • node 表示当前遍历的节点。
      • component 是一个列表,用于存储当前连通组件中的节点。

      dfs 函数的目标是遍历与 node 相关联的节点,并将它们添加到 component 中。

    3. visited 是一个集合,用于跟踪已访问的节点。一开始,它是空的。

    4. connected_components 是一个列表,用于存储找到的连通组件。开始时,它也是空的。

    5. 外部的 for 循环遍历图中的每个节点,以确保所有节点都被覆盖。对于每个节点,它执行以下操作:

      • 如果该节点尚未在 visited 中,表示它是一个新的连通组件的起始节点。
      • 创建一个新的空列表 component,用于存储该连通组件的节点。
      • 调用 dfs(node, component) 函数,开始深度优先搜索,并将所有与该节点相连的节点添加到 component 中。
      • component 添加到 connected_components 中,表示已找到一个连通组件。
    6. 最后,函数返回 connected_components 列表,其中包含了所有找到的连通组件。

    5.3  拓扑排序

            拓扑排序是用于确定有向图中节点的线性顺序,使得图中的每一条有向边都是从前面的节点指向后面的节点。在拓扑排序中,没有环路存在。

    应用示例

            假设有一个有向图如下,表示课程之间的依赖关系,您需要找到一个可以完成所有课程的顺序。如果存在环路,表示存在无法解决的依赖关系,您需要找到一个没有环路的顺序。

    1. import networkx as nx
    2. import matplotlib.pyplot as plt
    3. def topological_sort(graph):
    4. def dfs(node):
    5. visited.add(node)
    6. for neighbor in graph.get(node, []):
    7. if neighbor not in visited:
    8. dfs(neighbor)
    9. result.append(node)
    10. visited = set()
    11. result = []
    12. for node in graph:
    13. if node not in visited:
    14. dfs(node)
    15. return result[::-1]
    16. # 定义有向图的依赖关系
    17. courses = {
    18. 'CSC300': ['CSC100', 'CSC200'],
    19. 'CSC200': ['CSC100'],
    20. 'CSC100': [],
    21. 'CSC400': ['CSC300', 'CSC200'],
    22. }
    23. # 创建一个有向图
    24. G = nx.DiGraph(courses)
    25. # 调用拓扑排序算法
    26. topological_order = topological_sort(courses)
    27. if topological_order:
    28. print("Topological Order of Courses:", topological_order)
    29. else:
    30. print("No valid topological order (contains a cycle).")
    31. # 绘制有向图
    32. pos = nx.spring_layout(G)
    33. nx.draw(G, pos, with_labels=True, node_size=500, node_color='lightblue', font_size=10, font_color='black', font_weight='bold')
    34. # 添加边的标签
    35. labels = {}
    36. for node in G.nodes():
    37. labels[node] = node
    38. edge_labels = {(u, v): v for u, v in G.edges()}
    39. nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=8)
    40. plt.show()

    输出为:

            示例定义了topological_sort函数,用于执行拓扑排序。这个函数使用深度优先搜索(DFS)来查找图的拓扑排序。如果图中存在环路,该函数仍然会返回一个排序结果,但它不保证是一个有效的拓扑排序。

    5.4  在树结构中进行深度遍历

    1. import networkx as nx
    2. import matplotlib.pyplot as plt
    3. class TreeNode:
    4. def __init__(self, value):
    5. self.value = value
    6. self.children = []
    7. def add_child(self, child_node):
    8. self.children.append(child_node)
    9. def depth_first_search(node, graph, parent=None):
    10. if node is None:
    11. return
    12. graph.add_node(node.value) # 添加节点到图中
    13. if parent is not None:
    14. graph.add_edge(parent.value, node.value) # 添加边连接父节点和当前节点
    15. print(node.value) # 在DFS时输出节点值
    16. for child in node.children:
    17. depth_first_search(child, graph, node) # 递归遍历子节点
    18. # 创建一个较复杂的树结构
    19. root = TreeNode("A")
    20. b = TreeNode("B")
    21. c = TreeNode("C")
    22. d = TreeNode("D")
    23. e = TreeNode("E")
    24. f = TreeNode("F")
    25. g = TreeNode("G")
    26. h = TreeNode("H")
    27. i = TreeNode("I")
    28. root.add_child(b)
    29. root.add_child(c)
    30. b.add_child(d)
    31. b.add_child(e)
    32. c.add_child(f)
    33. c.add_child(g)
    34. g.add_child(h)
    35. h.add_child(i)
    36. # 创建一个有向图
    37. G = nx.DiGraph()
    38. # 执行深度优先搜索并创建图
    39. depth_first_search(root, G)
    40. # 绘制树结构图
    41. pos = nx.spring_layout(G)
    42. nx.draw(G, pos, with_labels=True, node_size=500, node_color='lightblue', font_size=10, font_color='black', font_weight='bold')
    43. # 添加边的标签
    44. labels = {}
    45. for node in G.nodes():
    46. labels[node] = node
    47. edge_labels = {(u, v): v for u, v in G.edges()}
    48. nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=8)
    49. plt.show()

             这段代码用于创建一个树结构,然后执行深度优先搜索(DFS),最后绘制树结构图并添加标签。以下是对代码的详细解析:

    1. 首先,定义了一个树结构的节点类 TreeNode,其中每个节点具有一个值和子节点列表。

    2. depth_first_search 函数中,执行深度优先搜索。它接受三个参数:

      • node:当前要处理的节点。
      • graph:用于构建树结构图的 NetworkX 有向图对象。
      • parent:父节点,用于添加边。

      在函数中,执行以下操作:

      • 添加当前节点到图中(graph.add_node(node.value))。
      • 如果存在父节点,添加从父节点到当前节点的边(graph.add_edge(parent.value, node.value))。
      • 打印当前节点的值,以在DFS期间输出节点值。
      • 递归遍历当前节点的子节点,使用当前节点作为父节点。
    3. 创建一个较复杂的树结构:

      • 根节点为 "A",有两个子节点 "B" 和 "C"。
      • 节点 "B" 有两个子节点 "D" 和 "E"。
      • 节点 "C" 有两个子节点 "F" 和 "G"。
      • 节点 "G" 有一个子节点 "H"。
      • 节点 "H" 有一个子节点 "I"。
    4. 创建一个 NetworkX 有向图对象 G,用于存储树结构图。

    5. 执行深度优先搜索,从根节点 "A" 开始。深度优先搜索会递归遍历树的每个分支,并在DFS期间输出节点值。

    6. 使用 NetworkX 绘制树结构图:

      • nx.spring_layout 用于确定节点的位置。
      • nx.draw 用于绘制节点和边,设置节点的大小、颜色和标签。
      • nx.draw_networkx_edge_labels 用于添加边的标签。
    7. 最后,通过 plt.show() 显示绘制的树结构图。

  • 相关阅读:
    数据结构哈希表
    八、开发者工具与单元测试
    Informatica使用操作流程及Expression(表达式转换)案例2
    html5与css3前端学习笔记
    JVM 图形化监控工具
    复习单片机:IO串转并(内含:1. 74HC595 芯片介绍+2. 硬件设计+3. 软件设计+4.原始代码+5. 实验现象)
    JPA联合主键
    Python 实现微信测试号情侣纪念消息推送(消息群发)
    GIS工具maptalks开发手册(二)03-02——示例之json格式添加绘制工具、渲染点、文字和多个面
    HTML中marquee标签的属性之多少?
  • 原文地址:https://blog.csdn.net/qq_35831906/article/details/133829680