C++单链表检测是否有环详解

4 篇文章 0 订阅

看到判断单链表是否有环的问题,想了好久才彻底明白,记录下来。文采有限,说的有些罗嗦。

a. 判断单链表是否有环。

b. 如果单链表有环,判断环长,入口点位置。

c. 判断链表长度。

        a这个问题网上有很多种解法,其中一种是快慢指针法。定义快慢两个指针fast和slow,快指针一次移动两个节点,慢指针一次移动一个节点,从表头开始遍历链表。分为两种情况:

        1.如果有环,快慢指针会在环中会和,然后停止遍历。

        2.如果没有环,快慢指针会遍历到链表结尾的空节点,然后停止遍历。

对于问题1,fast和slow指针必然会在环内会合,而且会合时slow指针移动位置不会超过一圈。

看到好多文章都这么说,我当时也有疑问,真的是这样吗?怎么证明呢?后来终于想明白了,具体如下:

必然会在环内相遇容易理解,当fast和slow都在环上的时候,设环长为circle,那么两个指针相距0到(circle-1)。0表示在同一点,也就是会和;(circle-1)表示fast在slow前一个点。slow指针每移动1步,fast指针移动2步,之间的距离减少1。最多(circle-1)步距离为0,即会和。slow指针刚进入环作为第一步,最多经过(circle-1)步和fast指针会和。这样上面的问题就解释完了。

对于问题b,如果判断出有环,记录会和点的位置P,继续向前移动slow指针,当再次走到P的时候,正好一圈,记录这个步数就是环长(记为circle)。设链表开始点到环入口长度是L,从环入口到P点的距离是X。那么当slow和fast会和时,slow指针经过的距离是Length=(L+X)。

这时,设新的指针Third指向链表开始,然后和slow指针一样,每次向后移动1步,slow指针和Third同时移动(fast指针不用移动),并判断是否重合。经过Length=(L+X)步,Third指针正好到达P点,走的就是slow原来走的路。此时slow也在P点。问题又出现了,为什么它在P点?

当slow与fast相遇的时候,由于fast的速度是slow的2倍,fast经过的距离是2*Length,slow再走Length的距离是2*Length,也必然在P点。

好了,目前slow和Third都在P点,他们会和了。由于他们每次都走1步,那么从环的开始点Start到P的这段距离,即他们在环上每点都是重合的。第一次会和的点就是环的入口点。

对于问题c,入口点求出来了,那么也能求出起始点到它的距离L,然后加上已经求出的环长circle,就能求出整个链表长度了。

下面这段C++代码验证以上的分析。

#include <iostream>

using namespace std;

struct Node{
    int data;
    Node* next;
};

//判断单链表是否存在环,参数circleNode是环内节点
bool hasCircle(Node *head, Node *&circleNode)
{
    Node *slow, *fast;
    slow = fast = head;
    while (fast != NULL && fast->next != NULL)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)
        {
            circleNode = fast;
            return true;
        }
    }
    return false;
}

//找到环的入口点
Node *findLoopPort(Node *head)
{
    //如果head为空,或者为单节点,则不存在环
    if (head == NULL || head->next == NULL) return NULL;

    Node *slow, *fast;
    slow = fast = head;

    //先判断是否存在环
    while (fast != NULL && fast->next != NULL)
    {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)
        {
            break;
        }
    }

    if (fast != slow) return NULL;  //不存在环

    fast = head;                    //快指针从头开始走,步长变成1
    while (fast != slow)            //两者相遇即为入口点
    {
        fast = fast->next;
        slow = slow->next;
    }
    return fast;
}

int main()
{
    Node head;
    head.data = 0;

    Node n1;
    n1.data = 1;
    head.next = &n1;

    Node n2;
    n2.data = 2;
    n1.next = &n2;

    Node n3;
    n3.data = 3;
    n2.next = &n3;

    Node n4;
    n4.data = 4;
    n3.next = &n4;

    Node n5;
    n5.data = 5;
    n4.next = &n5;

    Node n6;
    n6.data = 6;
    n5.next = &n6;
    n6.next = &n3;  //节点6指向节点3,形成一个环,入口为节点3

    Node *meetNode;
    bool ret = hasCircle(&head, meetNode);
    cout<<"has circle:"<<ret<<"meet node:"<<meetNode->data<<endl;

    Node* entrance;
    entrance = findLoopPort(&head);
    cout<<"entrance:"<<entrance->data<<endl;

    return 0;
}

程序参考http://wuchong.me/blog/2014/03/25/interview-link-questions/

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值