在一个有环的链表中找到交叉节点。

9 浏览
0 Comments

在一个有环的链表中找到交叉节点。

如何检测一个单链表是否有环?如果有环,如何找到环的起点,即环开始的节点。

0
0 Comments

找到链表中的环节点是一个常见的问题,可以通过运行两个指针来检测。这个过程被称为龟兔赛跑算法,得名于同名寓言故事。

首先,检查链表是否为空(head为null)。如果是空的,说明没有环存在,直接停止。

否则,将第一个指针tortoise指向第一个节点head,将第二个指针hare指向第二个节点head.next。

然后,循环直到hare为null(在只有一个元素的链表中可能已经为null),每次迭代将tortoise向前移动一步,hare向前移动两步。由于hare开始时领先并且速度更快,所以它会先到达链表的末尾(如果有的话)。

如果没有末尾(即存在一个环),它们最终会指向同一个节点,此时可以停止循环,说明找到了环中的某个节点。

下面是伪代码实现:

def hasLoop(head):
  return false if head = null
  tortoise = head
  hare = tortoise.next
  while hare != null:
    return false if hare.next = null
    hare = hare.next.next
    tortoise = tortoise.next
    return true if hare = tortoise
  return false
enddef

该算法的时间复杂度为O(n),因为访问的节点数(通过tortoise和hare)与节点数成正比。

一旦找到环中的一个节点,还有一个确定环的起点的O(n)的方法。

首先回到找到环中的某个元素的原始位置,但是不确定环的起点在哪里。

然后按照以下步骤进行:

1. 将hare向前移动,并将size设为1。

2. 只要hare和tortoise不同,就继续将hare向前移动,并每次增加size。这最终给出了环的大小,例如这种情况下是6。

3. 如果此时size为1,说明你已经在环的起点(在大小为1的环中,只有一个可能在环中的节点,所以它必须是第一个节点)。在这种情况下,直接返回hare作为起点,并跳过下面的步骤。

4. 否则,将hare和tortoise同时设置为链表的第一个元素,并将hare向前移动size次(在这种情况下是到7)。这样就得到了两个指针,它们之间的差正好是环的大小。

5. 然后,只要hare和tortoise不同,就同时将它们向前移动(hare以与tortoise相同的速度移动,我猜它已经因为第一次跑步而累了)。由于它们始终相距恰好size个元素,tortoise将会在与hare返回环的起点相同时达到环的起点。

下面是一个示例:

size  tortoise  hare  comment
----  --------  ----  -------
6         1     1  初始状态
                   7  hare前进六步
             2     8  1/7不同,所以一起前进
             3     3  2/8不同,所以一起前进
                      3/3相同,退出循环

因此,3是环的起点。由于这两个操作(检测环和发现环的起点)都是O(n)的,并且按顺序执行,整个过程的时间复杂度也是O(n)。

如果你想要对找到环的起点有一个更正式的证明,可以参考一些资源,如数学堆栈交换网站上的一个问题、维基百科上的循环检测页面,或者Peter Gammie于2016年4月17日发表的论文《龟兔算法》。

最后,有一个直观的例子可以帮助理解。想象一下模拟时钟中的时针和分针,它们以不同的速度运动,但最终会相遇。

总结起来,找到链表中的环节点和环的起点是常见的问题,可以使用龟兔赛跑算法来解决。这个算法通过两个指针在链表上移动,检测是否存在环,并找到环的起点。它的时间复杂度是O(n)。

0
0 Comments

链表中是否有循环是一个常见的问题,可以使用哈希映射或双指针方法来解决。

首先,我们来看一下使用哈希映射的方法。下面的代码使用哈希映射来判断链表是否有循环:

static bool isListHaveALoopUsingHashMap(Link *headLink) {
    map tempMap;
    Link * temp;
    temp = headLink;
    while (temp->next != NULL) {
        if (tempMap.find(temp) == tempMap.end()) {
            tempMap[temp] = 1;
        } else {
            return 0;
        }
        temp = temp->next;
    }
    return 1;
}

上述代码中,我们首先定义了一个哈希映射tempMap来存储每个节点的指针。然后,我们从链表的头部开始遍历每个节点,如果当前节点在tempMap中不存在,则将其添加到tempMap中;如果当前节点已经在tempMap中存在,则说明链表有循环,返回0;最后,如果遍历结束,说明链表没有循环,返回1。

然而,上述方法的空间复杂度较高,为O(n)。因此,我们可以使用双指针方法来优化。

双指针方法的思路是使用两个指针,一个快指针和一个慢指针,从链表的头部开始,快指针每次移动两个节点,慢指针每次移动一个节点。如果链表有循环,那么快指针最终会追上慢指针,即它们会指向同一个节点;如果链表没有循环,那么快指针会先到达链表的末尾。下面是使用双指针方法来判断链表是否有循环的代码:

static bool isListHaveALoopUsingTwoPointers(Link *headLink) {
    Link *fastPtr, *slowPtr;
    fastPtr = slowPtr = headLink;
    while (fastPtr != NULL && fastPtr->next != NULL) {
        fastPtr = fastPtr->next->next;
        slowPtr = slowPtr->next;
        if (fastPtr == slowPtr) {
            return 1;
        }
    }
    return 0;
}

上述代码中,我们首先定义了两个指针fastPtr和slowPtr,初始时它们都指向链表的头部。然后,我们使用一个循环来遍历链表,每次迭代中,fastPtr移动两个节点,slowPtr移动一个节点。在每次迭代中,我们都检查fastPtr和slowPtr是否指向同一个节点,如果是,则说明链表有循环,返回1;如果不是,则继续迭代。如果循环结束后还没有找到循环,则说明链表没有循环,返回0。

从上面的内容可以看出,使用哈希映射的方法可以判断链表是否有循环,但空间复杂度较高;而使用双指针方法可以在O(n)的时间复杂度下判断链表是否有循环,并且空间复杂度较低。因此,双指针方法是更好的方法。

0
0 Comments

链表中存在环的情况下,如何找到环的入口节点。

一种解决方法是使用“龟兔赛跑算法”(Tortoise and Hare algorithm)。该算法的核心思想是,使用两个指针从链表的起始位置出发,一个指针每次移动一个节点,另一个指针每次移动两个节点。如果链表中存在环,那么这两个指针最终一定会相遇。相遇点即为环的某个节点。

具体解决方法如下:

1. 初始化两个指针slow和fast,初始位置都为链表的起始节点。

2. 使用一个循环,每次将slow指针移动一个节点,fast指针移动两个节点。

3. 当slow和fast指针相遇时,证明链表中存在环。此时,将其中一个指针保持不动,另一个指针继续移动一个节点,直到两个指针再次相遇。记录下这个移动的步数P,即为环的周长。

4. 在链表起始节点处放置一个新的指针,将其移动P个节点。

5. 同时放置另一个指针在链表起始节点处,然后每次将这两个指针都向前移动一个节点,直到它们相遇的位置,即为环的入口节点。

这个方法的时间复杂度为O(n),其中n为链表的长度。相比于使用两个指针分别遍历链表和检查是否存在重复节点的O(n^2)解决方法,这个方法更加高效。

通过对“龟兔赛跑算法”的应用,我们可以找到链表中环的入口节点,解决了这个问题。这个算法的原理类似于模拟龟兔赛跑,通过不同的速度移动,最终相遇在环的某个节点处。这个算法在链表问题中有广泛的应用,是一个非常巧妙和高效的解决方法。

0