在单个链表中查找循环

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在单个链表中查找循环相关的知识,希望对你有一定的参考价值。

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

答案

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

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

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

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

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

考虑以下从3开始的循环:

head -> 1 -> 2 -> 3 -> 4 -> 5
                  ^         |
                  |         V
                  8 <- 7 <- 6

从1开始tortoise,2开始hare,它们具有以下值:

(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).

这是要遵循的过程:

  • 推进hare并将size设置为1
  • 然后,只要haretortoise不同,继续推进hare,每次增加size。这最终给出了循环的大小,在这种情况下为6。
  • 在这一点上,如果size1,那意味着you 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 returnhare`作为开始,并跳过下面的其余步骤。
  • 否则,将haretortoise都设置为列表的第一个元素,并将hare提前size次(在本例中为7)。这给出了两个与指针大小完全不同的指针。
  • 然后,只要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)并且顺序执行,所以整体结合在一起也是O(n)


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

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

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

def nextp(p, ld, sz):
    if p == ld + sz:
        return ld
    return p + 1

for size in range(1,1001):
    for lead in range(1001):
        p1 = 0
        p2 = 0
        while True:
            p1 = nextp(p1, lead, size)
            p2 = nextp(nextp(p2, lead, size), lead, size)
            if p1 == p2:
                print("sz = %d, ld = %d, found = %d" % (size, lead, p1))
                break
另一答案

另一种方法

检测循环:

  1. 创建一个列表
  2. 循环遍历链表,并继续将节点添加到列表中。
  3. 如果节点已经存在于List中,我们就有一个循环。

去除循环:

  1. 在上面的步骤#2中,在循环链接列表的同时,我们还跟踪前一个节点。
  2. 一旦我们在步骤#3中检测到循环,就将前一个节点的下一个值设置为NULL #码 def detect_remove_loop(head) 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')
另一答案

首先,创建一个节点

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();
} 
另一答案
                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;
                }
另一答案

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

另一答案

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

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

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

另一答案

你也可以使用哈希映射来查找链表是否有循环函数下面使用哈希映射来查找链表是否有循环

    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;
            }

以上是关于在单个链表中查找循环的主要内容,如果未能解决你的问题,请参考以下文章

c_cpp 在链表中查找循环的长度

数据结构:单向链表系列5--在链表中查找元素

如何在循环链表中查找循环起始节点?

面试:删除链表中的循环 - Java

递归查找链表中的第 n 个到最后一个元素

算法专区博文汇总