确定链表中是否有循环的最佳(停止)算法是什么?
[编辑] 时间和空间的渐近复杂度分析会很不错,因此可以更好地比较答案。
[编辑] 原始问题不是解决 outdegree > 1 的节点,但有一些讨论。这个问题更像是“在有向图中检测循环的最佳算法”。
确定链表中是否有循环的最佳(停止)算法是什么?
[编辑] 时间和空间的渐近复杂度分析会很不错,因此可以更好地比较答案。
[编辑] 原始问题不是解决 outdegree > 1 的节点,但有一些讨论。这个问题更像是“在有向图中检测循环的最佳算法”。
有两个指针遍历列表;让一个以两倍于另一个的速度迭代,并在每一步比较它们的位置。在我的头顶上,类似:
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),这是你能得到的最好的。
前提条件:跟踪列表大小(在添加或删除节点时更新大小)。
环路检测:
遍历列表大小时保留一个计数器。
如果计数器超过列表大小,则可能存在循环。
复杂度:O(n)
注意:计数器和列表大小的比较,以及列表大小的更新操作,必须是线程安全的。
取 2 个指针 *p 和 *q ,使用两个指针开始遍历链表“LL”:
1)指针p每次都会删除前一个节点并指向下一个节点。
2) 指针 q 每次只会向前走。
条件:
1) 指针 p 指向 null 并且 q 指向某个节点:存在循环
2)两个指针都指向null:没有循环
使用哈希表来存储已经看到的节点怎么样(您从列表的开头按顺序查看它们)?在实践中,您可以获得接近 O(N) 的结果。
否则,使用排序堆而不是哈希表将实现 O(N log(N))。
我想知道除了迭代之外是否还有其他方法 - 在您前进时填充一个数组,并检查当前节点是否已经存在于数组中......
如果循环不指向起点,康拉德鲁道夫的算法将不起作用。以下列表将使其成为无限循环:1->2->3->2。
DrPizza 的算法绝对是要走的路。
在这种情况下,OysterD 的代码将是最快的解决方案(顶点着色)
那真的会让我大吃一惊。我的解决方案最多通过列表两次(如果最后一个节点链接到倒数第二个矿脉),并且在常见情况下(无循环)只会通过一次。没有散列,没有内存分配等。
在这种情况下,OysterD 的代码将是最快的解决方案(顶点着色)
那真的会让我大吃一惊。我的解决方案最多通过列表两次(如果最后一个节点链接到倒数第二个矿脉),并且在常见情况下(无循环)只会通过一次。没有散列,没有内存分配等。
是的。我注意到这个公式并不完美,并改写了它。我仍然相信一个聪明的散列可能会执行得更快——只差一点点。不过,我相信您的算法是最好的解决方案。
只是为了强调我的观点:vertec 着色用于检测现代垃圾收集器的依赖循环,因此它有一个非常真实的用例。他们主要使用位标志来执行着色。
您将不得不访问每个节点来确定这一点。这可以递归地完成。为了阻止您访问已经访问过的节点,您需要一个标志来表示“已经访问过”。这当然不会给你循环。因此,不要使用位标志,而是使用数字。从 1 开始。检查连接的节点,然后将它们标记为 2 并递归直到网络被覆盖。如果在检查节点时遇到比当前节点少一个以上的节点,那么您就有一个循环。周期长度由差值给出。
两个指针在链表的头部初始化。一个指针每一步前进一次,另一个指针每一步前进两次。如果较快的指针再次遇到较慢的指针,则列表中存在循环。否则,如果更快的到达列表末尾,则没有循环。
下面的示例代码就是按照这个方案来实现的。较快的指针是 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。时间复杂度为 2n。看起来它不使用任何时间变量。
这是一个使用哈希表(实际上只是一个列表)来保存指针地址的解决方案。
def hash_cycle(node):
hashl=[]
while(node):
if node in hashl:
return True
else:
hashl.append(node)
node=node.next
return False
def has_cycle(head): counter = set()
while head is not None:
if head in counter:
return True
else:
counter.add(head)
head = head.next