如何确定链接列表是否只使用两个内存位置进行循环

时间:2009-01-30 07:51:59

标签: algorithm loops linked-list cycle

有没有人知道一种算法,用于查找链表是否只使用两个变量来遍历列表。假设您有一个链接的对象列表,它与哪种类型的对象无关。我在一个变量中有一个指向链表头部的指针,我只有一个其他变量来遍历列表。

所以我的计划是比较指针值以查看是否有任何指针相同。该列表的大小有限,但可能很大。我可以将两个变量都设置为头部,然后用另一个变量遍历列表,总是检查它是否等于另一个变量,但是,如果我确实打了一个循环,我将永远不会离开它。我认为它与遍历列表和比较指针值的不同速率有关。有什么想法吗?

7 个答案:

答案 0 :(得分:46)

我建议使用Floyd's Cycle-Finding Algorithm 又名 Tortoise and the Hare Algorithm。它具有O(n)复杂度,我认为它符合您的要求。

示例代码:

function boolean hasLoop(Node startNode){
  Node slowNode = Node fastNode1 = Node fastNode2 = startNode;
  while (slowNode && fastNode1 = fastNode2.next() && fastNode2 = fastNode1.next()){
    if (slowNode == fastNode1 || slowNode == fastNode2) return true;
    slowNode = slowNode.next();
  }
  return false;
}

有关维基百科的更多信息:Floyd's cycle-finding algorithm

答案 1 :(得分:17)

您可以使用Turtle and Rabbit算法。

维基百科也有一个解释,他们称之为“Floyd's cycle-finding algorithm”或“Tortoise and hare”

答案 2 :(得分:9)

绝对。一个解决方案确实可以用两个指针遍历列表,一个以两倍的速率行进。

从'slow'和'fast'指针开始,指向列表中的任何位置。运行遍历循环。如果任何时候“快速”指针与慢速指针一致,那么你就有了一个循环链表。

int *head = list.GetHead();
if (head != null) {
    int *fastPtr = head;
    int *slowPtr = head;

    bool isCircular = true;

    do 
    {
        if (fastPtr->Next == null || fastPtr->Next->Next == null) //List end found
        {
            isCircular = false;
            break;
        }

        fastPtr = fastPtr->Next->Next;
        slowPtr = slowPtr->Next;
    } while (fastPtr != slowPtr);

    //Do whatever you want with the 'isCircular' flag here
}

答案 3 :(得分:3)

我试图自己解决这个问题,并找到了一种不同的(效率较低但仍然是最优的)解决方案。

这个想法是基于在线性时间内反转单链表。这可以通过在迭代列表的每一步进行两次交换来完成。如果q是前一个元素(最初为空)并且p是当前,则交换(q,p->下一个)交换(p,q)将反转链接并同时推进两个指针。可以使用XOR完成交换,以防止必须使用第三个内存位置。

如果列表有一个循环,那么在迭代期间的某一点,您将到达指针已被更改的节点。您无法知道哪个节点,但通过继续迭代,交换一些元素两次,您再次到达列表的头部。

通过反转列表两次,列表在结果中保持不变,您可以根据是否到达列表的原始头来判断它是否有一个周期。

答案 4 :(得分:2)

int isListCircular(ListNode* head){
    if(head==NULL)
        return 0;
    ListNode *fast=head, *slow=head;
    while(fast && fast->next){
        if(fast->next->next==slow)
            return 1;
        fast=fast->next->next;
        slow=slow->next;
    }
    return 0;
}

答案 5 :(得分:1)

boolean findCircular(Node *head)
{
    Node *slower, * faster;
    slower = head;
    faster = head->next;
    while(true) {
        if ( !faster || !faster->next)
            return false;
        else if (faster == slower || faster->next == slower)
            return true;
        else
            faster = faster->next->next;
    }
}

答案 6 :(得分:0)

将此问题转移到下一步将识别循环(即,不仅仅是循环存在,而是确切地在列表中的位置)。 Tortoise和Hare算法可以用于相同的,但是,我们需要始终跟踪列表的头部。可以找到此算法的图示here