在单个链表中查找循环

时间:2012-04-23 06:05:09

标签: linked-list

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

13 个答案:

答案 0 :(得分:123)

你可以通过在列表中运行两个指针来检测它,这个过程被称为同名寓言之后的乌龟和野兔算法。

首先,检查列表是否为空(headnull)。如果是这样,就不可能循环,所以现在就停止。

否则,在第一个节点tortoise上启动第一个指针head,在第二个节点hare上启动第二个指针head.next

然后继续循环,直到harenull(在单元素列表中可能已经为真),将tortoise推进1,将hare推进为每个3迭代。野兔保证首先到达终点(如果 结束),因为它开始前进并且运行得更快。

如果没有结束(即,如果存在循环),它们最终将指向同一节点,您可以停止,因为知道您已在循环中找到某处节点。 / p>

考虑以下从head -> 1 -> 2 -> 3 -> 4 -> 5 ^ | | V 8 <- 7 <- 6 开始的循环:

tortoise

从1开始hare,在{2}开始(tortoise,hare) = (1,2) (2,4) (3,6) (4,8) (5,4) (6,6) ,它们采用以下值:

(6,6)

因为它们在hare变得相等,并且因为tortoise 总是在非循环列表中超出def hasLoop (head): return false if head = null # Empty list has no loop. tortoise = head # tortoise initially first element. hare = tortoise.next # Set hare to second element. while hare != null: # Go until hare reaches end. return false if hare.next null # Check enough left for hare move. hare = hare.next.next # Move hare forward two. tortoise = tortoise.next # Move tortoise forward one. return true if hare = tortoise # Same means loop found. endwhile return false # Loop exit means no loop. enddef ,这意味着您已经发现一个循环。

伪代码将是这样的:

O(n)

此算法的时间复杂度为O(n),因为访问的节点数(通过乌龟和野兔)与节点数成正比。


在循环中知道节点后,还有一个head -> 1 -> 2 -> 3 -> 4 -> 5 ^ | | V 8 <- 7 <- 6 \ x (where hare and tortoise met). 保证方法可以找到循环的 start

在循环中的某处找到一个元素之后让我们返回到原始位置,但是你不确定循环的开始位置。

hare

这是要遵循的过程:

  • 提前size并将1设为hare
  • 然后,只要tortoisehare 不同,就会继续前进size,每次增加size。这最终给出了循环的大小,在这种情况下为6。
  • 此时,如果1you must *already* be at the start of the loop (in a loop of size one, there is only one possible node that can *be* in the loop so it *must* be the first in that loop). In this case, you simply return,则表示hare hare`为开头,并跳过下面的其余步骤。
  • 否则,请将tortoisehare同时设置为列表的第一个元素,并将size提前7次提前{到{ {1}}在这种情况下)。这给出了两个不同的指针正好循环的大小。
  • 然后,只要haretortoise不同,就把它们放在一起(野兔以更稳重的速度跑,与乌龟的速度相同 - 我猜它已经累了第一次运行)。由于它们在任何时候都会保持size个完全相同的元素,tortoise hare 返回到循环的开头。

您可以通过以下演练了解到这一点:

size  tortoise  hare  comment
----  --------  ----  -------
   6         1     1  initial state
                   7  advance hare by six
             2     8  1/7 different, so advance both together
             3     3  2/8 different, so advance both together
                      3/3 same, so exit loop

因此3是循环的起点,因为这两个操作(循环检测和循环开始发现)都是O(n)并且是顺序执行的,所以整个过程也是{{ 1}}。


如果您想要一个更正式的证据,可以检查以下资源:

如果您只是支持该方法(非正式证明),您可以运行以下Python 3程序,该程序评估其大量大小(循环中有多少元素)和引入程序的可行性(循环开始前的元素。)

你会发现它总能找到两个指针相遇的点:

O(n)

答案 1 :(得分:34)

所选答案给出O(n * n)解决方案以找到循环的起始节点。这是一个O(n)解决方案:

一旦我们发现慢速A和快速B在循环中相遇,使其中一个静止而另一个继续每次步进一步,以确定周期的周长,比如P。

然后我们将一个节点放在头部并让它走P步,并将另一个节点放在头部。我们每次都将这两个节点前进一步,当它们第一次相遇时,它就是循环的起点。

答案 2 :(得分:6)

您也可以使用哈希映射来查找链接列表是否有循环,函数使用哈希映射来查找链接列表是否有循环

    static bool isListHaveALoopUsingHashMap(Link *headLink) {

        map<Link*, int> 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;
    }

两指针方法是最好的方法,因为时间复杂度是O(n)Hash Map所需的额外O(n)空间复杂度。

答案 3 :(得分:2)

我在Narasimha Karamanchi的数据结构书中读到了这个答案。

我们可以使用 Floyd循环查找算法,也称为龟和野兔算法。在这里,使用了两个指针;一个(比如slowPtr)由一个节点提前,另一个(比如fastPtr)由两个节点提前。如果单个链表中存在任何循环,它们肯定会在某个时刻相遇。

struct Node{
int data;
struct Node *next;

}

 // program to find the begin of the loop

 int detectLoopandFindBegin(struct Node *head){
      struct Node *slowPtr = head, *fastPtr = head;
      int loopExists = 0;
      // this  while loop will find if  there exists a loop or not.
      while(slowPtr && fastPtr && fastPtr->next){                                                  
        slowPtr = slowPtr->next;                      
        fastPtr = fastPtr->next->next;
        if(slowPtr == fastPtr)
        loopExists = 1;
        break;
      }

如果存在任何循环,那么我们将其中一个指针指向头部,现在将它们推进到单个节点。他们将会遇到的节点将是单个链表中循环的 start 节点。

        if(loopExists){      
             slowPtr = head;
             while(slowPtr != fastPtr){
               fastPtr = fastPtr->next;
               slowPtr = slowPtr->next;
             }
             return slowPtr;
          }
         return NULL;
        }

答案 4 :(得分:1)

以下代码将查找SLL中是否存在循环,如果存在循环,则返回起始节点。

int find_loop(Node *head){

    Node * slow = head;
    Node * fast =  head;
    Node * ptr1;
    Node * ptr2;
    int k =1, loop_found =0, i;

    if(!head) return -1;

    while(slow && fast && fast->next){
            slow = slow->next;
        /*Moving fast pointer two steps at a time */
            fast = fast->next->next;
            if(slow == fast){
                    loop_found = 1;
                    break;
            }

    }

    if(loop_found){
    /* We have detected a loop */
    /*Let's count the number of nodes in this loop node */

            ptr1  = fast;
            while(ptr1 && ptr1->next != slow){
                    ptr1 = ptr1->next;
                    k++;
            }
    /* Now move the other pointer by K nodes */
            ptr2 = head;

            ptr1  = head;
            for(i=0; i<k; i++){
                    ptr2 = ptr2->next;
            }

    /* Now if we move ptr1 and ptr2 with same speed they will meet at start of loop */

            while(ptr1 != ptr2){
                    ptr1  = ptr1->next;
                    ptr2 =  ptr2->next;
            }

    return ptr1->data;

}

答案 5 :(得分:1)

在大多数情况下,所有先前的答案都是正确的,但这是具有可视化和代码的逻辑的简化版本(适用于Python 3.7)

逻辑很简单,正如其他人解释的那样。我要创建乌龟/慢速和野兔/快速。如果我们以不同的速度移动两个指针,那么最终快将遇到慢速!您也可以将其视为大头钉圆形区域中的两个跑步者。如果快速奔跑的人不断绕行,那么它将遇到/超过慢奔跑的人。

因此,对于每次迭代,我们将以1速度移动龟/慢速指针,同时继续以2的速度递增或移动Hare / fast指针。一旦它们满足,我们便知道存在一个循环。这也称为Floyd's cycle-finding algorithm enter image description here

这是执行此操作的Python代码(注意has_cycle方法是主要部分):

#!/usr/bin/env python3
class Node:
    def __init__(self, data = None):
        self.data = data
        self.next = None
    def strnode (self):
        print(self.data)


class LinkedList:
    def __init__(self):
        self.numnodes = 0
        self.head = None


    def insertLast(self, data):
        newnode = Node(data)
        newnode.next = None
        if self.head == None:
            self.head = newnode
            return

        lnode = self.head
        while lnode.next != None :
            lnode = lnode.next
        lnode.next = newnode # new node is now the last node
        self.numnodes += 1

    def has_cycle(self):    
        slow, fast = self.head ,self.head  
        while fast != None:       
            if fast.next != None:
                 fast = fast.next.next
            else:
                 return False
            slow = slow.next  
            if slow == fast:
                print("--slow",slow.data, "fast",fast.data) 
                return True    
        return False


linkedList = LinkedList()
linkedList.insertLast("1")
linkedList.insertLast("2")
linkedList.insertLast("3")


# Create a loop for testing 
linkedList.head.next.next.next = linkedList.head; 
#let's check and see !
print(linkedList.has_cycle())

答案 6 :(得分:0)

boolean hasLoop(Node *head)
    {
      Node *current = head;
      Node *check = null;
      int firstPtr = 0;
      int secondPtr = 2;
      do {
        if (check == current) return true;
        if (firstPtr >= secondPtr){
            check = current;
            firstPtr = 0;
            secondPtr= 2*secondPtr;
        }
        firstPtr ++;
      } while (current = current->next());
      return false;
    }

另一个O(n)解决方案。

答案 7 :(得分:0)

当我查看所选答案时,我尝试了几个例子,发现: 如果(A1,B1),(A2,B2)......(AN,BN)是指针A和B的遍历
    其中A步骤1元素和B步骤2元素,Ai和Bj是A和B遍历的节点,AN = BN。
然后,循环开始的节点是Ak,其中k = floor(N / 2)。

答案 8 :(得分:0)

好吧 - 我昨天在接受采访时碰到了这个 - 没有可用的参考资料,我想出了一个非常不同的答案(当然开车回家......)因为链接列表是偶然的(并不总是我承认)分配使用malloc逻辑然后我们知道分配的粒度是已知的。在大多数系统上,这是8个字节 - 这意味着底部3位始终为零。考虑一下 - 如果我们将链表放在一个类来控制访问并使用0x0E的掩码进入下一个地址,那么我们可以使用低3位来存储一个破碎碎屑因此我们可以编写一个方法来存储我们的最后一个痕迹 - 说1或2 - 并交替他们。我们检查循环的方法然后可以逐步遍历每个节点(使用我们的下一个方法)并检查下一个地址是否包含当前的痕迹 - 如果它有,我们有一个循环 - 如果没有,那么我们将掩盖低3位并添加我们当前的痕迹。面包屑检查算法必须是单线程的,因为你不能同时运行其中的两个但它允许其他线程访问列表异步 - 通常有关添加/删除节点的警告。你怎么看?如果其他人觉得这是一个有效的解决方案,我可以写出样本课......只要想想有时新方法是好的,我总是愿意被告知我错过了这一点......谢谢所有马克

答案 9 :(得分:0)

另一种解决方案

检测循环:

  1. 创建列表
  2. 循环浏览链表,并继续将节点添加到链表中。
  3. 如果节点已经存在于列表中,则存在一个循环。

删除循环:

  1. 在上面的步骤2中,在循环浏览链接列表的同时,我们还跟踪了上一个节点。
  2. 在步骤3中检测到循环后,将上一个节点的下一个值设置为NULL

    #code

    def detect_remove_loop(头)

        cur_node = head
        node_list = []
    
        while cur_node.next is not None:
            prev_node = cur_node
            cur_node = cur_node.next
            if cur_node not in node_list:
                node_list.append(cur_node)
            else:
                print('Loop Detected')
                prev_node.next = None
                return
    
        print('No Loop detected')
    

答案 10 :(得分:0)

首先,创建一个节点

struct Node { 
    int data; 
    struct Node* next; 
}; 

全局初始化头指针

Struct Node* head = NULL;

在链接列表中插入一些数据

void insert(int newdata){

    Node* newNode = new Node();
    newNode->data = newdata;
    newNode->next = head;
    head = newNode;
}

创建一个函数detectLoop()

void detectLoop(){
    if (head == NULL || head->next == NULL){
        cout<< "\nNo Lopp Found in Linked List";
    }
    else{
        Node* slow = head;
        Node* fast = head->next;
        while((fast && fast->next) && fast != NULL){
            if(fast == slow){
                cout<<"Loop Found";
                break;
            }
            fast = fast->next->next;
            slow = slow->next;
        }
        if(fast->next == NULL){
            cout<<"Not Found";
        }
    }
}

从main()调用函数

int main() 
{ 
    insert(4);
    insert(3);
    insert(2);
    insert(1);

    //Created a Loop for Testing, Comment the next line to check the unloop linkedlist
    head->next->next->next->next = head->next;

    detectLoop();
    //If you uncomment the display function and make a loop in linked list and then run the code you will find infinite loop 
    //display();
} 

答案 11 :(得分:0)

                bool FindLoop(struct node *head)
                {
                    struct node *current1,*current2;

                    current1=head;
                    current2=head;

                    while(current1!=NULL && current2!= NULL && current2->next!= NULL)
                    { 
                          current1=current1->next;
                          current2=current2->next->next;

                          if(current1==current2)
                          {
                                return true;
                          }
                    }

                    return false;
                }

答案 12 :(得分:-1)

一种完全不同的方法: - 反转链表。 如果你再次到达头部时倒车,那么列表中有一个循环, 如果你得到NULL,那么没有循环。 总时间复杂度为O(n)