【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的路径。

# 定义示例图
GRAPH = {'A': ['B', 'C'],'B': ['D', 'E'],'C': ['F'],'D': [],'E': ['F'],'F': ['G'],'G': []
}# 定义DFS算法,查找从起始节点到目标节点的路径
def dfs(graph, start, end, path=[]):# 将当前节点添加到路径中path = path + [start]# 如果当前节点等于目标节点,返回找到的路径if start == end:return path# 如果当前节点不在图中,返回Noneif start not in graph:return None# 遍历当前节点的邻居节点for node in graph[start]:# 如果邻居节点不在已访问的路径中,继续DFSif node not in path:new_path = dfs(graph, node, end, path)# 如果找到路径,返回该路径if new_path:return new_path# 如果无法找到路径,返回Nonereturn None# 调用DFS算法查找从A到G的路径
path = dfs(GRAPH, 'A', 'G')
if path:print("Path from A to G:", path)
else:print("No path found.")

输出:

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

import networkx as nx
import matplotlib.pyplot as plt# 定义一个有向图的邻接列表表示
graph = {'A': ['B', 'C'],'B': ['A'],'C': ['A'],'D': ['E'],'E': ['D'],'F': [],
}def find_connected_components(graph):def dfs(node, component):visited.add(node)component.append(node)for neighbor in graph.get(node, []):if neighbor not in visited:dfs(neighbor, component)visited = set()connected_components = []for node in graph:if node not in visited:component = []dfs(node, component)connected_components.append(component)return connected_components# 查找连通组件
components = find_connected_components(graph)# 打印连通组件
for i, component in enumerate(components, start=1):print(f"Connected Component {i}: {component}")# 创建有向图
G = nx.DiGraph(graph)# 绘制图形
pos = nx.spring_layout(G)
nx.draw(G, pos, with_labels=True, node_size=500, node_color='lightblue', font_size=10, font_color='black', font_weight='bold')# 添加边的标签
labels = {}
for node in G.nodes():labels[node] = node
edge_labels = {(u, v): v for u, v in G.edges()}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=8)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  拓扑排序

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

应用示例

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

import networkx as nx
import matplotlib.pyplot as pltdef topological_sort(graph):def dfs(node):visited.add(node)for neighbor in graph.get(node, []):if neighbor not in visited:dfs(neighbor)result.append(node)visited = set()result = []for node in graph:if node not in visited:dfs(node)return result[::-1]# 定义有向图的依赖关系
courses = {'CSC300': ['CSC100', 'CSC200'],'CSC200': ['CSC100'],'CSC100': [],'CSC400': ['CSC300', 'CSC200'],
}# 创建一个有向图
G = nx.DiGraph(courses)# 调用拓扑排序算法
topological_order = topological_sort(courses)if topological_order:print("Topological Order of Courses:", topological_order)
else:print("No valid topological order (contains a cycle).")# 绘制有向图
pos = nx.spring_layout(G)
nx.draw(G, pos, with_labels=True, node_size=500, node_color='lightblue', font_size=10, font_color='black', font_weight='bold')# 添加边的标签
labels = {}
for node in G.nodes():labels[node] = node
edge_labels = {(u, v): v for u, v in G.edges()}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=8)plt.show()

输出为:

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

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

import networkx as nx
import matplotlib.pyplot as pltclass TreeNode:def __init__(self, value):self.value = valueself.children = []def add_child(self, child_node):self.children.append(child_node)def depth_first_search(node, graph, parent=None):if node is None:returngraph.add_node(node.value)  # 添加节点到图中if parent is not None:graph.add_edge(parent.value, node.value)  # 添加边连接父节点和当前节点print(node.value)  # 在DFS时输出节点值for child in node.children:depth_first_search(child, graph, node)  # 递归遍历子节点# 创建一个较复杂的树结构
root = TreeNode("A")
b = TreeNode("B")
c = TreeNode("C")
d = TreeNode("D")
e = TreeNode("E")
f = TreeNode("F")
g = TreeNode("G")
h = TreeNode("H")
i = TreeNode("I")root.add_child(b)
root.add_child(c)
b.add_child(d)
b.add_child(e)
c.add_child(f)
c.add_child(g)
g.add_child(h)
h.add_child(i)# 创建一个有向图
G = nx.DiGraph()# 执行深度优先搜索并创建图
depth_first_search(root, G)# 绘制树结构图
pos = nx.spring_layout(G)
nx.draw(G, pos, with_labels=True, node_size=500, node_color='lightblue', font_size=10, font_color='black', font_weight='bold')# 添加边的标签
labels = {}
for node in G.nodes():labels[node] = node
edge_labels = {(u, v): v for u, v in G.edges()}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=8)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() 显示绘制的树结构图。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/106765.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】.c源文件从编译到链接生成可执行程序的过程

本篇文章目录 1. 过程概览2. 编译与链接2.1 预编译/预处理2.2 编译2.3 汇编2.4 链接 3. 执行/运行环境 1. 过程概览 编译到链接是c语言的翻译环境,c语言还有执行环境。 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)…

2.Vue-从零开始搭建一个vue项目

题记 从零开始搭建一个vue项目,以下是操作的全过程。 安装Vue CLI脚手架 打开终端,运行以下命令全局安装Vue CLI脚手架: npm install -g vue/cli 查看 Vue CLI脚手架版本: vue -V 注意:查看vue版本的命令不是vue -V&a…

安捷伦N8485A射频传感器26.5GHz

安捷伦N8485A射频传感器 N8485A 是 Agilent 使用的 26.5 GHz 0.1 瓦射频传感器。电子测试设备传感器测量波形的功率,例如多音和调制射频 (RF) 波形。传感器使用二极管检测器收集高度精确的调制测量值。 用于存储校准系数的 EEPROM 消除了手动输入 10 MHz 至 26.5 GH…

15 | JPA 对 Web MVC 开发者做了哪些支持

我们使用 Spring Data JPA 的时候,一般都会用到 Spring MVC,Spring Data 对 Spring MVC 做了很好的支持,体现在以下几个方面: 支持在 Controller 层直接返回实体,而不使用其显式的调用方法;对 MVC 层支持标…

解读 | 自动驾驶系统中的多视点三维目标检测网络

原创 | 文 BFT机器人 01 背景 多视角三维物体检测网络,用于实现自动驾驶场景高精度三维目标检测,该网络使用激光雷达点云和RGB图像进行感知融合,以预测定向的三维边界框,相比于现有技术,取得了显著的精度提升。同时现…

树莓派:64位 RPI OS(Bookworm) 更换国内源

几天前新的RPI OS发布了。官方的发版说明里明确注明已经基于Debian Bookworm了。总的来说切到国内源(清华)跟Bullseye差不多,细节上只有一丢丢不同(non-free变成了non-free-firmware)。 老规矩,仍然是修改…

华为数通方向HCIP-DataCom H12-831题库(单选题:241-260)

第241题 某园区部署了IPV6进行业务测试,该网络中有4台路由器(R1R2、R3和R),运行OSPFV3实现Pv6网络的互联互通。有一台新的路由器R5需要接入网络进行测试,某工程师通过在R4的OSPFV3进程中引入直连路由,实现园区网内的设备能够访问R5的GEO/0/1口地址。关千该场景的描述,错误…

Spring(17) AopContext.currentProxy() 类内方法调用切入

目录 一、简介二、代码示例2.1 接口类2.2 接口实现类2.3 AOP切面类2.4 启动类(测试)2.5 执行结果 一、简介 背景: 在之前 Spring 的 AOP 用法中,只有代理的类才会被切入。例如:我们在 Controller 层调用 Service 的方式…

DID赛道前列的生物识别技术,开启Web3时代的大门—MXT

互联网发展的十字路口 互联网从上世纪90年代初发展至今,历经30年,她改变了整个人类的生活方式、沟通形式以及社会发展模式,她的影响早已渗透到了世界的各个角落。而如今,我们似乎正站在一个新的十字路口,一个互联网将…

155M传输分析仪 优劣势分析

D240S SDH测试模块,是FT100智能网络测试平台产品家族的一部分,是一个坚固耐用、锂电池超长供电的传统PDH/SDH测试解决方案,支持155Mbps到2.048Mbps速率的传输链路测试。支持在线和离线的传输链路的安装、维护和故障排除应用测试。 同时为经验…

Python自动化测试框架:unittest介绍

Unittest是Python中最常用的测试框架之一,它提供了丰富和强大的测试工具和方法,可以帮助开发者更好地保证代码质量和稳定性,本文就来介绍下Unittest单元测试框架。 1. 介绍 unittest是Python的单元测试框架,它提供了一套丰富的测…

VSCode怎么创建Java项目

首先安装好Java的开发环境:JDK在VSCode中安装适用于Java开发的插件。打开VSCode,点击左侧的扩展图标,搜索并安装Java Extension Pack插件。等待安装完成后,重启VSCode生效。创建一个新的Java项目,按下Ctrl Shift P&a…

稀里糊涂的转义

一、前言 前段时间挖机ERP系统出现一个问题,表单录入客户名称是 L & Q International Trading Limited,然后页面展示变成 L & Q International Trading Limited,即字符 &变成了&。 二、为什么要转义 &…

嵌入式C语言自我修养《GNU C编译器扩展语法》学习笔记

目录 一、C语言标准和编译器 二、指定初始化 三、宏构造“利器”:语句表达式 四、typeof与container_of宏 五、零长度数组 六、属性声明:section 七、属性声明:aligned 一、C语言标准和编译器 C语言标准的发展过程: ●…

基于观察者模式设计的框架-REB,使代码模块化

设计模式里面的观察者模式,一直是作者想去设计一套框架来阐述这一个模式,因此REB(Rice Event Broker)就是为了完成观察者模式的一个框架。 观察者模式 聊REB之前,我们聊聊观察者模式带给我们特性,他能对我们框架设计提供什么好处…

双十一期间如何抢占流量,打造品牌爆款产品

进入10月末,也就进入了电商行业的大促流量红利期。如何提前规划大促期间,店铺流量扩张的计划,提前抢占流量,是每一个品牌方都需要考虑的问题。今天为大家分享下双十一期间如何抢占流量,打造品牌爆款产品! 一…

Nginx请求参数解析

例: $arg_token 取的就是 uri?args 中 tokenxxx 的部分 $arg_PARAMETER #这个变量包含GET请求中,如果有变量PARAMETER时的值。$args #这个变量等于请求行中(GET请求)的参数,例如foo123&barblahblah;$binary_remote_addr #二进制的客户地…

浅谈安科瑞无线测温设备在俄罗斯某项目的应用

摘要:安科瑞ATE系列和ARTM-Pn无线测温设备适用于高低压柜的梅花触头,线缆,母排等位置对温度的实时监测。 Abstract: ATE series and ARTM-Pn are suitable for monitoring the real-time temperature of circuit breaker contact,cable,busb…

跨境电商:为民营经济注入新活力

中国的民营经济一直以来都是国家经济发展的中流砥柱,而近年来,跨境电商产业崭露头角,为民营经济注入了新的活力和机遇。本文将探讨跨境电商如何成为中国民营企业的助推引擎,以及其对民营经济的积极影响。 民营经济的支柱地位 中国…

ChatGPT AIGC 完成Excel跨多表查找操作vlookup+indirect

VLOOKUP和INDIRECT的组合在Excel中用于跨表查询,其中VLOOKUP函数用于在另一张表中查找数据,INDIRECT函数则用于根据文本字符串引用不同的工作表。具体操作如下: 1.假设在工作表1中,A列有你要查找的值,B列是你希望查询的工作表名称。 2.在工作表1的C列输入以下公式:=VLO…