Algorithm 测试链表是否有循环的最佳算法

Algorithm 测试链表是否有循环的最佳算法,algorithm,data-structures,linked-list,Algorithm,Data Structures,Linked List,确定链表中是否有循环的最佳(停止)算法是什么 [编辑]对时间和空间的渐近复杂性进行分析是很好的,这样可以更好地比较答案 [编辑]最初的问题不是解决outdegree>1的节点,但有一些关于它的讨论。这个问题更像是“在有向图中检测循环的最佳算法”。使用哈希表存储已经看到的节点(从列表开始按顺序查看它们)怎么样?在实践中,你可以取得接近O(N)的成绩 否则,使用排序堆而不是哈希表将实现O(N log(N))。有两个指针遍历列表;让其中一个以两倍于另一个的速度迭代,并在每一步比较它们的位置。在我的脑海

确定链表中是否有循环的最佳(停止)算法是什么

[编辑]对时间和空间的渐近复杂性进行分析是很好的,这样可以更好地比较答案


[编辑]最初的问题不是解决outdegree>1的节点,但有一些关于它的讨论。这个问题更像是“在有向图中检测循环的最佳算法”。

使用哈希表存储已经看到的节点(从列表开始按顺序查看它们)怎么样?在实践中,你可以取得接近O(N)的成绩


否则,使用排序堆而不是哈希表将实现O(N log(N))。

有两个指针遍历列表;让其中一个以两倍于另一个的速度迭代,并在每一步比较它们的位置。在我的脑海里,就像:

node* tortoise(begin), * hare(begin);
while(hare = hare->next)
{
    if(hare == tortoise) { throw std::logic_error("There's a cycle"); }
    hare = hare->next;
    if(hare == tortoise) { throw std::logic_error("There's a cycle"); }
    tortoise = tortoise->next;
}

O(n),这是你能得到的最好的结果。

我想知道除了迭代之外,还有什么其他方法-在你向前一步时填充数组,并检查当前节点是否已经存在于数组中…

如果循环不指向开始,Konrad Rudolph的算法将无法工作。下面的列表将使其成为一个无限循环:1->2->3->2

DrPizza的算法无疑是一条可行之路

在这种情况下,Oysted的代码将是最快的解决方案(顶点着色)

那真让我吃惊。我的解决方案最多通过列表两次(如果最后一个节点链接到倒数第二个矿脉),并且在常见情况下(无循环)仅通过一次。没有散列,没有内存分配,等等

在这种情况下,Oysted的代码将是最快的解决方案(顶点着色)

那真让我吃惊。我的解决方案最多通过列表两次(如果最后一个节点链接到倒数第二个矿脉),并且在常见情况下(无循环)仅通过一次。没有散列,没有内存分配,等等

对。我注意到这个公式并不完美,并对它进行了重新表述。我仍然相信一个聪明的散列可能会执行得更快——只需一点点。不过,我相信你的算法是最好的解决方案


只是强调一下我的观点:vertec着色被现代垃圾收集器用于检测依赖项中的循环,因此它有一个非常真实的用例。它们主要使用位标志来执行着色。

您必须访问每个节点才能确定这一点。这可以递归地完成。要停止访问已访问的节点,您需要一个标记来表示“已访问”。这当然不会给你循环。因此,使用数字代替位标志。1点开始。检查连接的节点,然后将它们标记为2,并递归,直到网络被覆盖。如果在检查节点时遇到比当前节点少一个以上的节点,则有一个循环。循环长度由差值给出。

前提条件:跟踪列表大小(在添加或删除节点时更新大小)

循环检测:

  • 遍历列表大小时保留一个计数器

  • 如果计数器超过列表大小,则可能存在循环

  • 复杂性:O(n)


    注意:计数器和列表大小之间的比较以及列表大小的更新操作必须是线程安全的。

    两个指针在列表的开头初始化。一个指针在每一步向前移动一次,另一个指针在每一步向前移动两次。如果更快的指针再次遇到较慢的指针,则列表中会有一个循环。否则,如果较快的循环到达列表的末尾,则不存在循环

    下面的示例代码是根据此解决方案实现的。较快的指针为pFast,较慢的指针为pSlow

    bool HasLoop(ListNode* pHead)
    {
        if(pHead == NULL)
            return false;
    
    
        ListNode* pSlow = pHead->m_pNext;
        if(pSlow == NULL)
            return false;
    
    
        ListNode* pFast = pSlow->m_pNext;
        while(pFast != NULL && pSlow != NULL)
        {
            if(pFast == pSlow)
                return true;
    
    
            pSlow = pSlow->m_pNext;
    
    
            pFast = pFast->m_pNext;
            if(pFast != NULL)
                pFast = pFast->m_pNext;
        }
    
    
        return false;
    }
    
    此解决方案在上提供。博客中还讨论了一个问题:当列表中存在循环/循环时,条目节点是什么?

    “Hack”解决方案(应该在C/C++中工作):

    • 遍历列表,并将
      next
      指针的最后一位设置为1
    • 如果找到指针已标记的元素,则返回true和循环的第一个元素
    • 在返回之前,重新设置指针,尽管我相信即使使用标记的指针,取消引用也会起作用

    时间复杂度为2n。看起来它没有使用任何时态变量。

    使用2个指针*p和*q,开始使用两个指针遍历链表“LL”:

    1) 指针p将每次删除前一个节点并指向下一个节点

    2) 指针q每次只能向前移动

    条件:

    1) 指针p指向null,q指向某个节点:存在循环


    2) 指向null的两个指针:没有循环

    这是一个使用哈希表(实际上只是一个列表)来保存指针地址的解决方案

    def hash_cycle(node):
        hashl=[]
        while(node):
            if node in hashl:
                return True
            else:
                hashl.append(node)
                node=node.next
        return False
    
    def具有循环(压头): 计数器=集合()


    哎呀,你实际上有一个bug。while循环应该是'while(hare&&hare=hare->next),否则您可以在末尾进行迭代。您不需要while(hare&&hare=hare->next)。唯一可以保护您的时间是如果列表为空(root为空)。否则,定义的while循环将在hare->next返回null时终止。@Herms,hare在循环体中被设置为hare->next,因此此时hare可能为null。Stepanov&McJones在编程元素中对此主题有非常详细的论述。第二章,你可以在这里读到,涵盖了这一点。顺便说一句,“在你的头顶上”可能暗示某人是你发明了这个算法,误导人们将其称为“DrPizza算法”(!)。让我们在应得的时候给予赞扬。Floyd早在1967年就发表了这篇文章:哈希表解是O(n)空间
    while head is not None:
        if head in counter:
            return True
        else:
            counter.add(head)
            head = head.next