当前位置: 首页 > news >正文

数据结构:如何判断一个链表中是否存在环(Check for LOOP in Linked List)

目录

初始思考:什么叫“链表有环”?

❌ 第一种直接想法(失败):我们是不是能“记住走过的节点”?

那我们换一个思路:我们能否只用两个指针来检测环?

第一步:定义两个指针

第二步:建立循环


初始思考:什么叫“链表有环”?

链表的本质是:

Node → Node → Node → NULL

但如果链表中某个节点的 next 指针不是 NULL,而是指向之前的某个节点:

Node → Node → Node  ↑         ↓  ←←←←←←←←←←

这就变成了一个“环形结构”。

❓我们怎么知道某个链表有没有环?

你无法一次看完所有节点。你只能从头开始,一个一个往后走:

while (p != NULL) {p = p->next;
}

正常情况下你最终会走到 p == NULL

但是如果链表有环,你会一直绕圈,永远也走不到 NULL,程序变成死循环。


❌ 第一种直接想法(失败):我们是不是能“记住走过的节点”?

想法:

每访问一个节点,就记住它;
如果某个节点我们第二次看到,就说明有环。

这个思路需要什么?
需要能“记住所有走过的节点”,并判断是否“已经访问过”

⚠️ 但我们没有 STL、没有哈希表、没有额外空间!

所以这个方法不符合第一性原则的最低资源要求,我们暂时排除。


那我们换一个思路:我们能否只用两个指针来检测环?

想象一下:

如果你在一个环形跑道上,有两个人:

  • 一个人慢跑(每次走一步)

  • 一个人快跑(每次走两步)

如果跑道没有环,快跑的人会直接跑出终点。

如果跑道是环形的,快跑的人迟早会“追上”慢跑的人!

这就是 Floyd’s Cycle Detection Algorithm(龟兔赛跑法)

我们现在从零构建它!


第一步:定义两个指针

slow = head;
fast = head;
  • slow 每次走一步:slow = slow->next

  • fast 每次走两步:fast = fast->next->next

 这一步是算法核心,“快指针”在追“慢指针” 

第二步:建立循环

变量含义
slow模拟“正常访问”的一步步访问
fast模拟“追赶检测”的两步步访问
slow==fast表示 fast 追上 slow,说明有环
fast==NULL表示链表已经结束,无环

问题:我们什么时候会出错?

  • 如果 fast == NULL:说明走到尾部

  • 如果 fast->next == NULL:再走一步就越界

我们必须确保两个指针不越界(避免访问 NULL 的 next):

while (fast != NULL && fast->next != NULL) {slow = slow->next;fast = fast->next->next;if (slow == fast) {// 二者相遇,说明有环}
}

❓为什么 slow 和 fast 相遇就说明有环?

因为只有在环中,快指针才有可能“绕回来追上慢指针”

就像操场上,跑得快的人终将追上慢跑的那个人。

如果没有环,fast 最终会变成 NULL 或 fast->next == NULL,循环终止。

如果链表中有环,两个指针最终会在环中某处相遇:

if (slow == fast)return 1;  // 有环

空链表或一个节点链表怎么办?

  • 如果 head == NULL:直接退出循环,返回 0 ✅

  • 如果只有一个节点:fast->next == NULL,也会直接退出 ✅

 完整代码

int hasLoop(struct Node* head) {struct Node* slow = head;struct Node* fast = head;// Step 1: 同时从头开始,每次走一步和两步while (fast != NULL && fast->next != NULL) {slow = slow->next;             // 慢指针走一步fast = fast->next->next;       // 快指针走两步if (slow == fast)              // 相遇 → 有环return 1;}// Step 2: 如果快指针走到了链表尾部,说明无环return 0;
}

时间 & 空间复杂度分析

复杂度说明
时间复杂度O(n),最坏情况 fast 会走到链表尽头(或追上 slow)
空间复杂度O(1),只用了两个指针变量,无需额外空间

我们从“什么都不会”出发,只知道我们:

  • 只能走 ->next

  • 不允许记录历史

  • 想办法“检测重复访问”

最终我们想到“相遇 → 有环”的思路,从而构建出用两个指针的检测算法。

http://www.dtcms.com/a/314129.html

相关文章:

  • JSqlParser学习笔记 快速使用JSqlParser
  • 从exec到Shell:深度解析Linux进程等待,程序替换与自主Shell实现
  • 电脑一键重装系统win7/win10/win11无需U盘(无任何捆绑软件图文教程)
  • OBS 基础 21 充满某个源的策略
  • Android GPU测试
  • 电子电气架构 ---智能电动汽车嵌入式软件开发过程中的block点
  • 【Linux指南】软件安装全解析:从源码到包管理器的进阶之路
  • 移动端生产网页设计误区:工业级操作场景下的手势交互创新
  • 【Django】-3- 处理HTTP响应
  • AUTOSAR CP:深度揭秘APPL层(Application Layer)!SWC分配策略与端口交互的终极指南
  • IntelliJIDEA上传GitHub全攻略
  • 国产智能三防手机哪款最好?这款支持单北斗、5G-A、IP68
  • 进一步分析云手机的优势有哪些?
  • chatgpt plus简单得,不需要求人,不需要野卡,不需要合租,不需要昂贵的价格
  • 手机防沉迷新招:安卓手机如何成为管理iPhone的遥控器?
  • Redis 实现互斥锁解决Redis击穿
  • Realme手机怎样相互远程控制?Realme可以被其他手机远程控制吗?
  • GPTs——定制的小型智能体
  • 微算法科技(NASDAQ: MLGO)开发量子边缘检测算法,为实时图像处理与边缘智能设备提供了新的解决方案
  • vue3+天地图。添加标注和点击当前去掉其他的标注
  • 1. 什么是柯里化
  • SpringBoot自动装配原理
  • XSS的原型链污染1--原型链解释
  • 如何选择一个容易被搜索引擎发现的域名?
  • C++ 入门基础(4)
  • CAD格式转换器HOOPS Exchange:全方位支持HOOPS系列产品
  • 20250805问答课题-实现TextRank + 问题分类
  • 解锁高并发LLM推理:动态批处理、令牌流和使用vLLM的KV缓存秘密
  • VUE+SPRINGBOOT从0-1打造前后端-前后台系统-整体示例
  • Memcached缓存与Redis缓存的区别、优缺点和适用场景