【问题标题】:List cycle detection [closed]列表周期检测[关闭]
【发布时间】:2019-11-07 09:54:03
【问题描述】:

给定一个链表,返回循环开始的节点。如果没有循环,返回null

使用兔子和乌龟算法检测到循环后,我看不懂 相等如何从起始地址和循环检测地址移动导致提供循环开始的地址。

    ListNode* Solution::detectCycle(ListNode* A) {

    ListNode* p=A;
    ListNode* q=A;
    while(p!=NULL && q!=NULL && q->next!=NULL)
    {
        p=p->next;
        q=q->next->next;
        if(p==q)
        {
            p=A;
            while(p!=q)
            {
                p=p->next;
                q=q->next;
            }
            return p;
        }
    }
    return NULL;

    }

【问题讨论】:

  • 我已经知道代码中发生了什么。问题是当 p==q 我们检测到一个循环但是当一个指针从 p 的头部移动到 q 的指针相等的时间(检测到循环的地方)时,为什么我们会得到循环开始的地址的开头。

标签: c++ loops linked-list cycle


【解决方案1】:

循环中的第一个节点是另一个节点指向的节点:

可以做的是遍历列表,保留所有节点地址并检查地址何时已记录。这个地址是循环开始节点。

示例代码:

    #include "stdio.h"
#include <iostream>
#include <vector>

struct ListNode
{
    struct ListNode* next;
};

ListNode* detectCycle(ListNode* A) {

    ListNode* p = A;
    ListNode* q = A;
    while (p != NULL && q != NULL && q->next != NULL)
    {
        p = p->next;
        q = q->next->next;
        if (p == q)
        {
            p = A;
            while (p != q)
            {
                p = p->next;
                q = q->next;
            }
            return p;
        }
    }
    return NULL;
}


template < typename T>
std::pair<bool, int > findInVector(const std::vector<T>& vecOfElements, const T& element)
{
    std::pair<bool, int > result;

    // Find given element in vector
    auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element);

    if (it != vecOfElements.end())
    {
        result.second = distance(vecOfElements.begin(), it);
        result.first = true;
    }
    else
    {
        result.first = false;
        result.second = -1;
    }

    return result;
}


int main()
{
    ListNode a, b, c, d, e, f;   // a -> b -> c -> d ->e -> f -> c;

    a.next = &b;
    b.next = &c;
    c.next = &d;
    d.next = &e;
    e.next = &f;
    f.next = &c;


    ListNode* p = detectCycle(&a);

    std::cout << p;

    std::vector<ListNode*> v;

    v.push_back(&a);

    ListNode* it = a.next;

    while (findInVector(v, it).first == false)
    {
        v.push_back(it);
        it = it->next;
    }

    std::cout << " first is " << v.at(findInVector(v, it).second) << " " << &c; 
}

【讨论】:

  • 问题不在于您如何做到这一点,而在于 OP 的算法为何有效。您无需再次遍历列表。
  • 确实如此。它与问题无关。
【解决方案2】:

它起作用是因为当你在里面时

if (p == q)
{

你肯定知道:

  1. 你正处于一个循环中
  2. 从该点到循环开始(设置p=A)的距离与
  3. 从列表开始到循环开始的距离。

    这就是为什么您可以将其中一个指针重置到列表的开头并以相同的速度移动它们。当他们相遇时,他们在循环的开始相遇。

考虑两个迭代器pq,速度分别为v_p=1v_q=2=2*v_p。假设循环的长度为n,并且它从节点号A &lt; n 开始。当较慢的迭代器到达A 时,较快的迭代器位于位置2A。在他们相遇之前需要多少次迭代k?在哪个节点?

情况由以下全等式描述:

  • A + k*v_p = 2A + 2*k*v_p mod(n)
  • 2*A + 2*k*v_p = A + k*v_p mod(n)
  • A + 2*k*v_p = k*v_p mod(n)
  • A +k*v_p = 0 mod(n)
  • A +k = 0 mod(n) !!!

有解决方案k = n-A

这意味着他们将在较慢的迭代器的 k=n-A 次迭代后遇到的两个指针。这意味着它们将在循环开始之前在 A 节点相遇,我们可以利用这个事实从列表的开头计算 A 节点来推断循环的起点。

同样的推理也适用于A &gt; n。 要进一步阐明这些一致性的含义,请查看以下图片:


我写了一封关于这个算法的blog article。查看更多信息。

【讨论】:

  • 你没有解释为什么你的答案中的#2是真的。
  • 我提供了一个包含所有细节的链接:)
  • 是的,这就是我要问的为什么移动相等时间有效。一旦我通过那篇文章做对了,我会承认的。
  • 检查我的最新编辑。应该清楚为什么点 2 有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-09
  • 2021-10-15
  • 2012-06-27
相关资源
最近更新 更多