Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $YECBGYFECGEAFWHA as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2

Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $BBWFDDBHHYHDXXAB as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2
为什么图或树的遍历算法会陷入死循环_创想鸟

为什么图或树的遍历算法会陷入死循环

图或树的遍历算法之所以会陷入死循环,其最核心、最普遍的原因在于待遍历的“图”数据结构中,存在着一个或多个“环路”,而遍历算法在执行过程中,又缺少一个有效的“已访问”状态记录机制。这套问题的产生,主要涉及五个关键因素:图结构中存在“环路”、遍历过程中缺少“已访问”状态的记录机制、深度优先搜索的递归实现导致“无限递归”、广度优先搜索的队列处理不当、以及未能正确处理“非连通图”的多个起点

为什么图或树的遍历算法会陷入死循环为什么图或树的遍历算法会陷入死循环

具体来说,一个“环路”的存在,意味着从图中某个节点出发,沿着一系列的边进行移动,最终,能够再次回到这个出发点。如果一个遍历算法,在访问过一个节点后,没有为其打上“我已经来过这里”的标记,那么,当它顺着环路,再次回到这个已访问过的节点时,就会将其,视为一个全新的、未被发现的节点,并再次,沿着它走过的旧路,重新开始一次新的遍历。这个过程,会周而复始,永不终止,从而将程序,拖入无尽的循环之中。

一、基础概念:理解“图”与“树”

在深入探讨“为何会死循环”之前,我们必须首先,在概念上,对“图”和“树”这两个数据结构,建立一个清晰、准确的认知。

1. 什么是图?

在计算机科学中,,是一种用于表示“对象”与“对象”之间“关联关系”的、非线性的数据结构。它由两个基本元素构成:

顶点:代表了一个独立的“对象”或“实体”。例如,一个社交网络中的“用户”,或一张地图上的“城市”。边:代表了两个顶点之间的“关联关系”。例如,用户之间的“好友关系”,或城市之间的“航线”。根据边的方向性,图又可以分为“有向图”(边有明确的方向,如A指向B)和“无向图”(边没有方向,A与B相互连接)。

2. 什么是树?

树,是一种非常特殊的、受到更多约束的“图”。一个数据结构,要被称为“树”,它必须同时满足两个核心条件:

它必须是“连通的”,即从任何一个顶点出发,都能到达其他任何一个顶点。它绝对不能,包含任何“环路”

3. 核心概念:“环路”

环路,是指在图中,存在一条路径,其起点和终点,是同一个顶点。例如,在一个图中,存在这样的路径:顶点A -> 顶点B -> 顶点C -> 顶点A。

这个“环路”的概念,是理解本文所有问题的“钥匙”。根据定义,树状结构,是绝对不允许存在环路的。而一个通用的、普通的图,则完全可以,包含一个甚至多个复杂的环路。

因此,一个设计正确的遍历算法,在遍历一棵“树”时,通常是“天生安全”的,因为它无论如何行走,都永远不会“回到过去”。但是,当同一个算法,被应用于一个可能包含“环路”的“图”时,如果它没有一套额外的“防范机制”,那么,陷入“死循环”的风险,就变得极高。

二、遍历的双雄:深度优先与广度优先

图的遍历,最经典的,有两种核心算法:深度优先搜索和广度优先搜索。

**1. 深度优先搜索 **

深度优先搜索的策略,如同在探索一个“迷宫”时,始终坚持“一条路走到黑”

执行过程:从一个起始顶点出发,访问它。然后,从它所有“尚未被访问”的邻居中,任选一个,再对这个邻居,进行同样地、深入地探索。直到当前路径的尽头,再也没有“未被访问”的邻居了,程序,才会“回溯”到上一个“岔路口”,去探索另一条“未曾走过”的路。实现方式:这种“后进先出”的回溯行为,天然地,就非常适合,用“递归”或一个显式的“”数据结构来实现。

**2. 广度优先搜索 **

广度优先搜索的策略,则更像是在水中,投下一颗石子,所产生的“涟漪”。它是一种“逐层向外”的探索方式。

执行过程:从一个起始顶点出发,访问它。然后,一次性地,访问它“所有”的、尚未被访问的邻居(这构成了“第一层”涟漪)。然后,再依次地,从这些“第一层”的邻居出发,去访问它们所有的、尚未被访问的邻居(这构成了“第二层”涟漪),如此反复,直至所有可达的顶点,都被访问完毕。实现方式:这种“先进先出”的逐层处理行为,天然地,就需要借助一个“队列”数据结构来实现。

三、死循环的“元凶”:未被标记的“环路”

现在,让我们来看看,当上述这两种经典的算法,遇到了一个包含了“环路”的、且算法自身又缺乏“防范机制”的图时,会发生怎样的“灾难”。

1. 问题的重现

假设,我们有如下一个简单的、包含了“环路”的有向图:

A -> B

B -> C

C -> A (这条边,构成了 A->B->C->A 的环路)

2. 深度优先搜索的“无限递归”

如果我们,从顶点A开始,进行一次“天真”的(即,没有“已访问”标记的)深度优先搜索,其执行过程(以递归为例)将是:

调用 DFS(A):访问A。找到A的邻居B,于是,递归调用 DFS(B)调用 DFS(B):访问B。找到B的邻居C,于是,递归调用 DFS(C)调用 DFS(C):访问C。找到C的邻居A,于是,递归调用 DFS(A)调用 DFS(A):访问A。找到A的邻居B,于是,递归调用 DFS(B)。……

我们发现,程序,进入了一个A -> B -> C -> A -> ...的、永不终止的无限递归调用链。其最终的,也必然的结局,就是因为耗尽了“调用栈”的内存空间,而抛出一个“栈溢出”的致命错误。

3. 广度优先搜索的“无限入队”

如果我们,从顶点A开始,进行一次“天真”的广度优先搜索,其执行过程将是:

访问A。将A的所有邻居(即B),加入队列。当前队列:[B]。从队列中,取出B,并访问它。将B的所有邻居(即C),加入队列。当前队列:[C]。从队列中,取出C,并访问它。将C的所有邻居(即A),加入队列。当前队列:[A]。从队列中,取出A,并访问它。将A的所有邻居(即B),加入队列。当前队列:[B]。……

我们发现,程序,同样地,进入了一个在队列中,反复地“存入B、取出B、存入C、取出C、存入A、取出A……”的、永不终止的死循环。这个程序,可能不会像递归那样,因为“栈溢出”而快速地崩溃,但它会永远地运行下去,持续地,消耗着中央处理器的资源和内存。

四、解决方案:建立“已访问”集合

要“斩断”这个由“环路”所导致的“无限循环”,其唯一的、也是最根本的解决方案,就是为我们的遍历算法,赋予“记忆”

1. 核心思想:“凡走过,必留痕”

这个“记忆”,在算法的实现中,通常,是一个被称为“已访问”的集合(可以使用“哈希集合”或“布尔数组”来实现)。

其核心思想是,在算法的整个生命周期中,维护这份“已访问”列表。在即将访问任何一个“新”的顶点之前,都必须,首先,查阅一下这份“记忆”,看看,我们“之前,是否已经来过这里?”

2. 修正后的深度优先搜索

Java

// visitedSet 是一个在遍历开始前创建的、全局的集合void correctedDFS(Node node, Set visitedSet) {    // 第一步:检查“记忆”,如果已访问过,则立即返回,斩断循环    if (visitedSet.contains(node)) {        return;    }    // 第二步:如果未访问,则立即“留下痕迹”,并进行访问    visitedSet.add(node);    System.out.println("访问节点: " + node.name);    // 第三步:继续探索其邻居    for (Node neighbor : node.getNeighbors()) {        correctedDFS(neighbor, visitedSet);    }}

通过在函数入口处,增加的这短短两三行“检查与标记”的代码,我们就为深度优先搜索,安装上了强大的“环路刹车”。

3. 修正后的广度优先搜索

Java

void correctedBFS(Node startNode) {    Queue queue = new LinkedList();    Set visitedSet = new HashSet();    // 将起点,同时,放入队列和“已访问”集合    queue.add(startNode);    visitedSet.add(startNode);    while (!queue.isEmpty()) {        Node currentNode = queue.poll();        System.out.println("访问节点: " + currentNode.name);        for (Node neighbor : currentNode.getNeighbors()) {            // 在将任何一个新邻居“入队”之前,都必须,先检查它是否已被访问            if (!visitedSet.contains(neighbor)) {                // 如果未访问,则同时,进行“标记”和“入队”                visitedSet.add(neighbor);                queue.add(neighbor);            }        }    }}

五、在实践中“防范”

要将上述的理论,转化为工程实践中的可靠保障,我们需要流程和工具的支撑。

将遍历逻辑“模块化”:应将这些经过了充分验证的、包含了“已访问”集合逻辑的、健壮的图遍历算法,封装为可被团队复用的、通用的“工具类”或“库函数”编写“边界”单元测试:一个用于测试图算法的、完备的单元测试集,必须,强制性地,包含一个或多个,专门用于检验算法在“有环图”上,是否能正常终止的测试用例。代码审查与规范:团队的编码规范中,应明确指出,在处理任何可能存在“环路”的数据结构时,都必须考虑并实现“已访问”的检查机制。这份规范,可以被沉淀和共享在像 Worktile 这样的通用协作平台的知识库中。而在 PingCode 这样的研发管理平台中,代码审查是其核心的流程环节,审查者,应将“检查是否存在不安全的遍历逻辑”,作为一个重要的审查点。


常见问答 (FAQ)

Q1: “树”的遍历,需不需要“已访问”集合?

A1: 理论上,不需要。因为“树”的严格定义,就是“无环的连通图”。因此,在遍历一棵“完美”的树时,你永远不会,重复地,访问到同一个节点。但是,在工程实践中,为了代码的“健壮性”(以防止输入的数据,并非一棵“严格”的树),增加一个“已访问”的检查,是一种更安全的、防御性的编程习惯。

Q2: 深度优先搜索和广度优先搜索,哪个更容易陷入死循环?

A2: 在都没有“已访问”检查的情况下,两者,在遇到“环路”时,都必然会陷入死循环。只是,其“表现形式”不同:深度优先搜索,通常,会因为“无限递归”,而快速地,以“栈溢出”的方式崩溃;而广度优先搜索,则会因为“无限入队”,而陷入一个不会自动崩溃、但会持续消耗资源的死循环。

Q3: 什么是“有向无环图”?它的遍历有什么特殊之处?

A3: “有向无环图”,是一种特殊的图,它虽然有向,但却保证不存在任何环路。这种数据结构,在工程中,应用极其广泛,例如,用于描述任务的“依赖关系”(A必须在B之前完成),或构建软件的“编译顺序”。对于它,有一种特殊的、极其重要的遍历算法,叫做“拓扑排序”。

Q4: 除了栈溢出或死循环,错误的图遍历还会导致什么问题?

A4: 即便程序,因为某种巧合,没有陷入死循环,一个没有“已访问”检查的遍历,也可能会,对同一个节点,进行“多次重复的访问和处理”。如果,你的业务逻辑,是“每访问一个节点,就将其计数加一”,那么,这种重复访问,就会导致最终的计算结果,完全错误。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:百晓生,转转请注明出处:https://www.chuangxiangniao.com/p/638996.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
12款类似terllo的项目管理软件盘点?2025年全新整理
上一篇 2025年11月12日 12:46:51
有什么好用的项目流程管理软件?对比8大主流项目过程管理系统
下一篇 2025年11月12日 12:47:30

相关推荐

  • Linux文件和目录管理常见命令

    Linux文件和目录管理依赖于ls、cd、mkdir、rm、cp、mv等核心命令,用于浏览、创建、删除、复制和移动文件与目录;通过find、du、grep等命令可查找文件、定位大文件并清理磁盘空间;使用rename、mmv或脚本可实现批量重命名;为安全起见,应谨慎使用rm命令,推荐结合-i选项或使用…

    2026年9月21日
    100
  • idea恢复初始化

    答案:通过关闭IDEA并删除配置、缓存目录及插件数据,可将其重置为初始状态。具体步骤依次为:彻底退出程序;删除系统中对应的JetBrains文件夹(Windows在AppData,macOS在Library,Linux在.config和.cache);可选删除项目中的.idea和.iml文件;重启后…

    2026年9月20日
    000
  • Linux输出文本echo命令应用

    echo命令不仅能输出文本,还可结合变量、转义序列和重定向实现动态内容生成、文件创建与追加、配置修改、管道处理及脚本调试,其行为在Bash、Zsh和Dash等shell中因内置实现不同而存在差异,尤其在转义序列处理上需注意使用-e选项或改用printf以保证一致性。 echo 命令在 Linux 中…

    2026年9月20日
    000
  • idea如何回到上一步

    使用快捷键可快速返回上一步操作位置:Windows/Linux为Ctrl+Alt+←,macOS为Cmd+Option+←,该操作称为“Back”,适用于跳转后返回原代码位置。 在使用 IntelliJ IDEA 时,如果想回到上一步的操作位置,比如刚才编辑或查看的代码位置,可以通过以下几种方式快速…

    2026年9月20日
    300
  • 打开DeepSeek官网 deepseek在线版立即使用

    DeepSeek官网在线版可通过https://www.deepseek.com访问,用户点击“开始对话”即可使用智能对话、代码生成、长文本处理等功能,开发者还可申请API密钥集成至自有系统,支持多语言调用与安全配置。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepS…

    2026年9月12日
    200
  • Linux命令行中pwd、cd命令的完整讲解

    pwd命令显示当前目录的完整路径,cd命令用于切换目录。例如pwd输出如/home/alice/Documents;cd /path/to/dir切换到指定路径,cd ..返回上一级,cd ~回到用户家目录,cd -在最近两个目录间切换,配合使用可高效导航文件系统。 pwd 和 cd 是 Linux…

    2026年9月10日
    200
  • VSCode快捷键:键盘流操作大全

    掌握VSCode快捷键可实现键盘流编程,提升效率。1. 快速导航:Ctrl+P打开文件,Ctrl+Tab切换标签,Ctrl+G跳转行号,F12跳转定义,Alt+←/→返回光标位置;2. 高效编辑:Ctrl+D多选单词,Ctrl+L选行,Shift+Alt+方向复制行,Ctrl+Shift+K删除行,…

    2026年9月10日
    100
  • VSCode语言支持:多编程环境配置

    首先安装各语言官方扩展并配置解释器路径,再通过launch.json设置调试参数,结合tasks.json定义构建任务,最后统一代码风格实现高效多语言开发。 VSCode 本身是一个轻量级但功能强大的代码编辑器,支持多种编程语言。要实现多编程环境的高效开发,关键在于正确配置语言支持和相关工具链。下面…

    2026年9月9日
    200
  • VSCode注释文档生成工具配置

    VSCode中通过Document This插件和ESLint集成可快速生成JSDoc注释;2. 安装插件后使用Ctrl+Alt+D快捷键自动生成函数、类的注释模板;3. 可自定义作者、日期等模板字段并结合eslint-plugin-jsdoc强制规范注释,提升代码可读性与维护性。 VSCode 中…

    2026年9月9日
    100
  • idea 恢复默认

    重置IntelliJ IDEA需删除配置和缓存目录以恢复默认设置。首先关闭软件,删除Windows下C:Users用户名AppDataRoamingJetBrainsIntelliJIdea或macOS/Linux对应路径的IntelliJIdea文件夹;推荐同时清除Local或Caches下的缓存…

    2026年9月9日
    400
  • VS Code语言支持:嵌入式语言与语法注入配置

    嵌入式语言和语法注入可提升VS Code对多语言文件的处理能力。通过embeddedLanguages配置,编辑器能将特定文本(如字符串)按目标语言高亮,例如将string.regexp映射为regex语言;语法注入则利用TextMate规则将一种语言的解析规则注入到另一种语言的作用域中,如在Han…

    2026年9月9日
    100
  • Claude 4.5 刚刚发布,能连肝 30 多个小时,史上最卷 AI 诞生

    Claude 4.5 刚刚发布,能连肝 30 多个小时,史上最卷 AI 诞生Claude 4.5 刚刚发布,能连肝 30 多个小时,史上最卷 AI 诞生Claude 4.5 刚刚发布,能连肝 30 多个小时,史上最卷 AI 诞生Claude 4.5 刚刚发布,能连肝 30 多个小时,史上最卷 AI 诞生

    论编程能力的极致内卷,还得看 Anthropic 的 Claude。 就在今天,Anthropic 正式推出全新升级版模型——Claude Sonnet 4.5。 先看硬核表现:在衡量真实编码实力的 SWE-bench Verified 测试中,Claude Sonnet 4.5 一举登顶榜首,成为…

    2026年9月8日 用户投稿
    200
  • Linux sticky bit命令示例

    Sticky Bit是一种特殊权限,用于目录以限制文件删除权限,仅允许文件所有者、目录所有者或root用户删除或重命名其中文件;在ls -l输出中以t或T表示,可通过chmod +t或chmod 1777设置,用chmod -t或chmod 777取消,常用于/tmp等公共可写目录,防止用户误删他人…

    2026年9月8日
    200
  • ThinkPHP6中的依赖注入

    依赖注入是现代php开发中非常重要的概念,它可以帮助开发者更好地管理类之间的依赖关系,提高代码的可扩展性和可重用性。在php框架thinkphp6中,依赖注入也得到了很好的支持。 在ThinkPHP6中,我们可以通过注解方式或配置文件的方式进行依赖注入。下面我们具体来看一下这两种方式的使用方法。 首…

    用户投稿 2026年9月7日
    200
  • 利用ThinkPHP6实现路由分组

    在现代web开发中,路由是一个至关重要的组成部分。它帮助我们将请求映射到相应的控制器方法,并且可以根据不同的url路径来执行不同的操作。在一些复杂的应用中,可能需要将路由进行分组,以便更好地组织和管理。本文将介绍如何在thinkphp6中实现路由分组。 ThinkPHP6是一款基于PHP的高性能We…

    用户投稿 2026年9月7日
    000
  • 在ThinkPHP6中使用远程调试

    thinkphp6是一个易于学习且功能强大的php框架。在开发项目时,很可能会面临一些难以定位的问题,如数据库连接问题、代码错误等。为了解决这些问题,我们需要调试程序。在这篇文章中,我们将介绍如何在thinkphp6中使用远程调试。 什么是远程调试? 远程调试是一种在不同计算机或设备之间的调试技术。…

    用户投稿 2026年9月7日
    100
  • Linux Shell编程的实例教程

    awk [-field-separator] ‘commands’ input-file(s) 基本模式 awk -F’:’ …  使用#分隔 awk ‘{print $0}’ a.txt  立即进入“豆包AI人工智…

    用户投稿 2026年9月7日
    200
  • Yii框架的一些基础知识

    yii是一款流行的面向对象php框架,它的全称是“yes it is”,表示“是的,它就是这样的”。它的设计目标是高效、快速、安全和易于使用,因此被广泛应用于大型web应用程序的开发中。在这篇文章中,我们将介绍yii框架的一些基础知识,帮助新手更好地了解这个框架。 MVC架构 Yii框架采用了基于M…

    用户投稿 2026年9月5日
    100
  • 详解华为手机微信分身操作步骤

    华为手机微信分身功能是指在手机上同时登录两个微信账号,并且能够实现两个微信账号的隔离使用。这项功能能够帮助用户更方便地管理工作和个人生活,避免混淆。下面将详细介绍在华为手机上如何进行微信分身操作。 步骤一:进入手机设置 首先,打开华为手机的主屏幕,在桌面上找到“设置”应用,点击进入。 步骤二:查找“…

    用户投稿 2026年9月5日
    000
  • Safari网页版入口 Safari直接打开

    Safari网页版可通过官网https://www.apple.com/safari/直接访问,支持跨平台浏览,页面加载快且适配多语言;具备节能、防跟踪、支持现代网页标准等技术优势,并与Apple生态无缝集成,提供简洁界面、阅读器模式和手势导航等优质用户体验。 Safari网页版入口 Safari直…

    2026年9月3日
    200

发表回复

登录后才能评论
关注微信