在 C++ 中检测并移除链表中的环

2025年3月17日 | 阅读 8 分钟

在本文中,我们将讨论如何在 C++ 中使用不同的方法检测和移除链表中的循环。

创建一个名为 detectAndRemoveLoop() 的函数,该函数验证给定链表是否存在循环。如果存在,它将移除循环并返回 true。如果列表中不存在循环,则返回 false。下图描绘了一个带有循环的链表。下面的列表必须使用 detectAndRemoveLoop() 方法更改为 1->2->3->4->5->NULL。

Detect and Remove Loop in a Linked List in C++

编写一个 C 函数来检测链表中的循环。

  • 在尝试移除循环之前,我们必须首先识别循环。循环可以使用上面讨论的方法找到。我们所要做的就是获取指向循环最后一个节点的指针,以终止循环。考虑上图中值为 5 的节点。一旦我们知道哪个节点是最后一个节点,我们就可以将此链中的下一个节点设置为 NULL 以终止循环。
  • 我们可以快速应用哈希或访问节点方法来获取指向最后一个节点的指针。概念很简单:最后一个节点是其下一个节点已被访问(或哈希)的第一个节点。
  • 弗洛伊德循环检测 技术也可以用于查找和移除循环。弗洛伊德算法中的慢指针和快指针会在循环节点处相遇。此循环节点可用于移除循环。当弗洛伊德技术检测到循环时,有两种不同的方法可以中断循环。

方法 1:(分别检查每个)

已知 弗洛伊德循环检测 过程在快指针和慢指针在同一位置碰撞时结束。此外,我们知道这个公共点是循环节点之一(上图中的 2、3、4 或 5)。将此位置放入指针变量中,例如 ptr2。之后,从链表的头部开始,通过单独检查每个节点来确定每个节点是否可从 ptr2 到达。当链表中的节点可到达时,我们可以获取指向该节点前一个节点的指针,表明该节点代表循环的开始。

输出

Linked List after removing Loop 
50 20 15 4 10 

方法 2:(更好的解决方案)

  1. 此技术也需要 弗洛伊德循环检测
  2. 通过使用弗洛伊德循环检测技术查找循环,获取指向循环节点的指针。
  3. 计算循环中有多少个节点。设 k 为计数。
  4. 一个指针应固定在头部,另一个指针应固定在距离头部第 k 个节点处。
  5. 如果我们将两个指针以相同的速度移动,它们将在循环的起始节点处碰撞。
  6. 获取指向循环最后一个节点的指针,并将其下一个节点设置为 NULL。

程序

让我们举一个例子来说明如何在 C++ 中检测和移除链表中的循环。

输出

Detect and Remove Loop in a Linked List in C++

复杂度分析

时间复杂度: O(N),其中 N 是树中的节点数

辅助空间: O(1)

方法 3:(不计算循环中的节点数)

无需计算循环中的节点数。在识别循环之后,如果我们以相同的速度移动快指针和慢指针直到快指针不相遇,它们将在循环的开始处碰撞。

这是如何工作的?

弗洛伊德循环检测 算法导致慢指针和快指针碰撞。下图描绘了发现循环时的场景。

Detect and Remove Loop in a Linked List in C++

我们可以从上图中得出结论。

从上面的方程,我们可以得出以下结论

因此,如果我们将两个指针再次以相同的速度移动,一个指针(我们称之为 slow)将从链表的头节点开始,而另一个指针(我们称之为 fast)将从相遇点开始。当慢指针到达循环的开始处(已移动 m 步)时,快指针也已移动了 m 步,因为它们现在以相同的速度移动。它们将在开始处碰撞,因为快指针从 k 开始,并且 m+k 是 n 的倍数。

程序

输出

Detect and Remove Loop in a Linked List in C++

复杂度分析

时间复杂度: O(N),其中 N 是树中的节点数

辅助空间: O(1)

方法 4:哈希(哈希链表节点的地址)

我们可以通过在无序映射中哈希链表节点的地址来检查元素是否已存在于映射中。我们必须将最后一个节点的下一个指针设置为 NULL,因为如果它存在,我们已经通过循环到达了一个已存在的节点。

程序

输出

Detect and Remove Loop in a Linked List in C++

复杂度分析

时间复杂度: O(N),其中 N 是树中的节点数。

辅助空间: O(N),其中 N 是树中的节点数(由于哈希)。