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

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

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

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

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

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

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)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月12日 12:46:51
下一篇 2025年11月12日 12:47:30

相关推荐

  • 上外边距未生效

    标题:探究margintop失效的原因及解决方法 导言:在进行网页设计或者开发过程中,经常会遇到某些元素的margintop属性失效的情况,造成布局上的问题。本文将探究margintop失效的原因,并提供解决该问题的具体代码示例。 一、margintop属性失效的可能原因 盒模型问题:当元素的盒模型…

    2025年12月24日
    000
  • 深度剖析程序设计中必不可少的数据类型分类

    【深入解析基本数据类型:掌握编程中必备的数据分类】 在计算机编程中,数据是最为基础的元素之一。数据类型的选择对于编程语言的使用和程序的设计至关重要。在众多的数据类型中,基本数据类型是最基础、最常用的数据分类之一。通过深入解析基本数据类型,我们能够更好地掌握编程中必备的数据分类。 一、基本数据类型的定…

    2025年12月24日
    000
  • 生成的html代码怎么在记事本运行_记事本运行生成html代码方法【教程】

    服务器IP无法解析时,可通过记事本编写HTML文件并用浏览器运行来本地测试网页:一、用记事本输入HTML代码,另存为.html文件;二、双击文件或右键选择浏览器打开;三、右键用记事本修改代码并保存后,在浏览器刷新即可查看更新内容。 如果您尝试访问某个网站,但服务器无法访问,则可能是由于服务器 IP …

    2025年12月23日
    000
  • 代码保存为html文件后怎么运行_保存后html文件运行方法【教程】

    1、直接右键HTML文件选择浏览器打开即可本地运行;2、通过浏览器菜单使用Ctrl+O加载文件;3、用VS Code等编辑器配合Live Server插件实现热更新预览;4、对于含JS/CSS外链或异步请求的项目,需用npx http-server启动本地服务器,通过http://localhost…

    2025年12月23日
    000
  • 打完代码怎么让它运行html_完成代码后运行html步骤【指南】

    首先保存HTML文件为.html格式,如index.html;然后通过双击文件或右键用浏览器打开;也可在编辑器中使用Live Server等功能实时预览;最后可创建书签或快捷方式方便重复访问。 如果您已经编写完HTML代码,想要在浏览器中查看页面效果,需要按照正确的方式打开和运行该文件。以下是将编写…

    2025年12月23日
    000
  • html代码好了怎么不在浏览器运行_禁html在浏览器运行设置【设置】

    首先检查文件是否以.html为扩展名并正确命名,接着通过浏览器地址栏输入file:///路径访问文件,然后为浏览器快捷方式添加–allow-file-access-from-files参数以解除本地文件限制,最后确认代码包含DOCTYPE声明及完整标签结构并通过W3C校验工具检测语法正确…

    2025年12月23日
    000
  • Mac用CodeRunner一键运行HTML并弹出浏览器预览

    首先安装并配置CodeRunner,创建自定义HTML Preview语言类型,设置运行命令为open $filename且不启用终端运行,接着开启自动保存功能确保代码实时生效,最后通过系统快捷键设置将Run命令绑定到Cmd+R实现一键预览。 如果您在Mac上编写HTML代码,希望借助轻量级工具实现…

    2025年12月23日
    000
  • Linux用dmenu快速启动HTML相关学习应用

    首先配置dmenu并绑定快捷键,再编写Shell脚本集中管理HTML学习工具,最后通过脚本集成浏览器文档资源快捷入口,实现一键启动应用与网页。 如果您希望通过快捷键快速启动与HTML学习相关的应用程序,但每次都需要手动查找或输入命令,可以利用dmenu结合自定义脚本实现高效访问。以下是具体操作步骤:…

    2025年12月23日
    000
  • Mac Bear标签页同时打开HTML源码和CSS样式

    Bear不支持HTML与CSS标签页式编辑,仅能通过代码块编写并导出预览,建议搭配VS Code等专业工具实现双栏实时开发。 在 Mac 版的 Bear 笔记应用中,无法直接以标签页形式同时打开 HTML 源码和 CSS 样式进行编辑。Bear 是一款专注于简洁写作的 Markdown 笔记工具,它…

    2025年12月23日
    000
  • Mac终端用file命令快速检测HTML文件编码类型

    使用file命令可快速检测Mac上HTML文件的编码类型。打开终端,输入file -I yourfile.html,查看输出中的charset字段,如charset=utf-8表示UTF-8编码;结合ls、for循环与grep可批量处理并过滤显示多个.html文件的编码信息,提升检测效率。 如果您需…

    2025年12月23日
    000
  • 手机HTML网页编辑器入口 HTML编辑器手机在线免费

    手机HTML网页编辑器入口位于https://www.tutorialspoint.com/codingground,该平台支持多语言在线编码、实时预览、无需安装、适配移动端,提供语法高亮、示例模板、多标签编辑、文件导出与分享功能,兼容安卓和iOS系统,适合初学者学习与小型项目开发。 手机HTML网…

    2025年12月23日
    000
  • HTML id 属性唯一性:深入理解与最佳实践

    html `id` 属性在整个文档中必须保持唯一。虽然非唯一 `id` 可能不会立即导致页面崩溃,但它会引发浏览器警告,并严重影响 javascript 对元素的精确操作以及 css 样式的预期应用。本文将深入探讨 `id` 唯一性的重要性、非唯一 `id` 带来的潜在问题,并提供确保前端代码健壮性…

    2025年12月23日
    000
  • 如何嵌入图片html_HTML图片嵌入(img标签/背景图)方法

    使用img标签插入内容性图片,需设置src和alt属性;2. 使用CSS background-image添加装饰性背景图,便于控制样式;3. 正确使用相对或绝对路径确保图片加载;4. 根据语义合理选择方法以提升可访问性与性能。 在网页中显示图片,常用的方法有两种:使用 img 标签 直接插入图片,…

    2025年12月23日 好文分享
    000
  • HTML定义列表怎么用_HTML的dl dt dd标签使用教程

    HTML定义列表()用于表示术语与定义的语义化结构,由和标签组成,适用于名称-值对内容,如词汇表、FAQ等。它在语义上优于无序或有序列表,能提升可访问性和SEO。正确使用包括一个对应多个或多个共享一个,避免用作布局工具。通过CSS可实现垂直或水平布局,并借助Flexbox和媒体查询实现响应式设计,增…

    2025年12月22日
    000
  • 如何创建HTML中的无序列表

    无序列表在网页设计中用于提升内容可读性与信息架构,常用于导航菜单、产品特性、FAQ等场景,通过和标签构建,支持嵌套实现层级结构,并可用CSS自定义样式如符号类型、图片项目符及伪元素装饰,增强视觉表现与用户体验。 在HTML中创建无序列表其实非常直接,你只需要用到 (unordered list)和 …

    2025年12月22日
    000
  • 如何保持文本格式不变

    要保持文本格式不变,需根据需求选择合适格式:若需保留视觉与布局,使用PDF或.docx;若为纯文本或代码,应选用UTF-8编码的纯文本文件,并用专业编辑器处理,避免隐藏格式与乱码。 要保持文本格式不变,核心在于理解“不变”的语境是什么,以及你所处理的文本是“富文本”还是“纯文本”。通常,这意味着你需…

    2025年12月22日
    000
  • 如何设置链接无跳转

    设置链接无跳转可通过前端JavaScript阻止默认行为或后端重定向实现。前端使用event.preventDefault()阻止跳转,可在点击时执行自定义逻辑,如弹窗或异步请求,必要时通过window.location.href手动跳转。后端如Node.js Express可通过记录点击日志后再重…

    2025年12月22日
    000
  • HTML中如何实现度量单位

    HTML中实现度量单位的关键是正确使用CSS提供的绝对单位(如px、pt)和相对单位(如em、rem、vw、vh、%),根据场景选择合适单位以实现响应式设计和布局灵活性。 HTML中实现度量单位的关键在于正确使用CSS,它允许你指定元素的大小、间距和其他属性,并附带各种度量单位。理解这些单位及其适用…

    2025年12月22日
    000
  • HTML中如何实现电话输入框

    使用实现电话号码输入框,可提升移动端输入体验和语义化;通过pattern属性进行客户端格式验证,配合title提供友好提示;结合autocomplete、inputmode、JavaScript实时格式化与验证、清晰placeholder及无障碍设计,全方位优化用户体验。 在HTML中实现电话号码输…

    2025年12月22日
    000
  • 如何实现弹出式菜单

    实现弹出式菜单需结合HTML结构、CSS样式与JavaScript交互,通过按钮触发菜单显示,利用CSS控制初始隐藏及过渡效果,JavaScript处理点击事件、外部关闭与键盘导航,并通过ARIA属性和语义化标签提升可访问性,同时针对不同设备采用响应式设计,如桌面端使用下拉菜单、移动端采用汉堡包菜单…

    2025年12月22日
    000

发表回复

登录后才能评论
关注微信