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

时间:2008-08-29 09:30:06

标签: algorithm data-structures linked-list

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

[编辑]对时间和空间的渐近复杂性的分析将是甜蜜的,因此可以更好地比较答案。

[编辑]原始问题未解决具有outdegree的节点> 1,但有一些谈论它。这个问题更像是“在有向图中检测周期的最佳算法”。

13 个答案:

答案 0 :(得分:50)

有两个指针在列表中迭代;让一个以另一个的速度迭代,并比较它们在每一步的位置。在我的头顶,像:

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),这是你能得到的最好的。

答案 1 :(得分:1)

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

循环检测:

  1. 在遍历列表大小时保留一个计数器。

  2. 如果计数器超出列表大小,则可能存在循环。

  3. 复杂性:O(n)

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

答案 2 :(得分:1)

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

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

2)指针q每次仅在正向方向上。

条件:

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

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

答案 3 :(得分:0)

如何使用哈希表存储已经看过的节点(从列表的开头按顺序查看它们)?在实践中,你可以获得接近O(N)的东西。

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

答案 4 :(得分:0)

我想知道除了迭代之外是否还有其他方式 - 在你前进时填充一个数组,并检查当前节点是否已经存在于数组中......

答案 5 :(得分:0)

如果循环没有指向开头,Konrad Rudolph的算法将不起作用。以下列表将使其成为无限循环:1-> 2-> 3-> 2.

DrPizza的算法绝对是最佳选择。

答案 6 :(得分:0)

  

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

那真让我感到惊讶。我的解决方案最多通过列表两次(如果最后一个节点链接到倒数第二个lode),并且在常见情况下(无循环)将只进行一次传递。没有散​​列,没有内存分配等等。

答案 7 :(得分:0)

  
    

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

  
     

那真让我感到惊讶。我的解决方案最多通过列表两次(如果最后一个节点链接到倒数第二个lode),并且在常见情况下(无循环)将只进行一次传递。没有散​​列,没有内存分配等等。

是。我注意到这个配方并不完美,并且已经改写了它。我仍然相信一个聪明的哈希可能表现得更快 - 一头发。我相信你的算法 是最好的解决方案。

仅仅强调我的观点:椎骨着色用于检测现代垃圾收集器的依赖性循环,因此它有一个非常真实的用例。他们大多使用位标志来执行着色。

答案 8 :(得分:0)

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

答案 9 :(得分:0)

两个指针在列表的头部初始化。一个指针在每个步骤转发一次,另一个指针在每一步转发两次。如果更快的指针再次遇到较慢的指针,则列表中有一个循环。否则,如果较快的一个到达列表的末尾,则没有循环。

以下示例代码是根据此解决方案实现的。更快的指针是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;
}

此解决方案可在my blog上找到。博客中讨论了更多问题:当列表中存在循环/循环时,什么是入口节点?

答案 10 :(得分:0)

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

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

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

答案 11 :(得分:0)

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

def hash_cycle(node):
    hashl=[]
    while(node):
        if node in hashl:
            return True
        else:
            hashl.append(node)
            node=node.next
    return False

答案 12 :(得分:0)

def has_cycle(head):     计数器= set()

while head is not None:
    if head in counter:
        return True
    else:
        counter.add(head)
        head = head.next