什么是检测链表中是否存在循环的最佳(停机)算法?
[编辑] 分析时间和空间的渐进复杂度会更好,这样答案可以更好地进行比较。
[编辑] 最初的问题没有涉及出度> 1的节点,但有一些讨论。 那个问题更多的是“检测有向图中循环的最佳算法”。
什么是检测链表中是否存在循环的最佳(停机)算法?
[编辑] 分析时间和空间的渐进复杂度会更好,这样答案可以更好地进行比较。
[编辑] 最初的问题没有涉及出度> 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)是最好的情况,不能再优化了。
while (hare && hare = hare->next)
,否则你可能会在迭代到末尾时继续循环。 - 1800 INFORMATION前置条件:跟踪列表大小(每当添加或删除节点时更新大小)。
循环检测:
在遍历列表时保持计数器。
如果计数器超过列表大小,则可能存在循环。
复杂度:O(n)
注意:必须使计数器与列表大小的比较以及列表大小的更新操作具有线程安全性。
拿两个指针 *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
两个指针初始化在链表头部。一个指针每次前进一步,另一个指针每次前进两步。如果快指针再次遇到慢指针,则链表中存在循环。否则,如果快指针到达链表末尾,则不存在循环。
下面的示例代码是根据此解决方案实现的。快指针为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;
}
next
指针的最后一位设置为1。您需要访问每个节点来确定这一点。这可以通过递归完成。为了防止您访问已经访问过的节点,您需要一个标志来表示“已经访问过”。当然,这并不能给您循环。因此,不要使用位标志,而是使用数字。从1开始。检查连接的节点,然后将其标记为2并递归,直到覆盖整个网络。如果在检查节点时遇到一个比当前节点少一个以上的节点,则说明存在一个循环。差异给出了循环长度。
使用哈希表来存储已经遍历过的节点(你按照列表开始的顺序查看它们)怎么样?实际上,你可以接近O(N)的效果。
否则,使用排序堆而不是哈希表将实现O(N log(N))。
我想知道是否有其他方法,而不仅仅是迭代地进行 - 在向前移动时填充数组,并检查当前节点是否已经存在于数组中...