C语言 环形链表

环形链表

链表中有一种不是循环链表,也不是单链表的结构

这种结构的链表当你走到最后一个结点的时候它的指针并不指向NULL而是指回链表中的某一个结点,这个结点可能是链表中的任何一个结点。

判断环形链表

再环形链表中最后一个结点不指向NULL所以我们就没有了表示符,直接遍历时找不到最后一个结点的,但是在没有明确表示这个链表是否是环形链表时我们无法判定这是个十分长的链表只是还没遍历到最后一个结点还是它就是一个环形链表若一直遍历的话我们便陷入了死循环了。所以我们需要先判定这个链表是否是环形链表。

因为环形链表最后一个结点能指向任何一个链表中的结点,我们不能使用特定结点作为标识符来遍历。

bool hasCycle(struct ListNode *head) 
{
    struct ListNode * quick=head;
    struct ListNode * slow = head;
    while(quick&&quick->next)
    {
        slow=slow->next;
        quick=quick->next->next;
        if(slow==quick)
        return 1;
    }
    return 0;
    
}

如上代码,我们这里使用两个指针作为快慢指针,因为当我们进入环形后,快指针会一直在环中循环等待慢指针,当满慢指针也进入到循环中便变成了环中的快慢追逐战直到两个指针的相遇。所以我们定义两个指针从第一个结点开始,快指针每次走两步,慢指针每次走一步,形成逻辑上的速度差。若是链表只是一个单链表那必会有最后一个结点指向NULL,当快指针找到了最后一个结点便确定着就是一个单链表了。但因为若此单链表中只有一个或两个结点的话,我们的快指针一次两步可能会形成越界成为野指针,所以我们的循环结束条件需要把这两个情况都算进去。

若是环形链表我们只需要让两个指针一直按规律走直到相遇便可确定链表是环形,因为快指针是不会到达NULL的。

快慢指针是否一定会相遇

指针的快慢只是形式上的,是人为创造出来的一种情况,实现上指针是直接跳过了中间的结点的,并不会在每一个结点都逗留,那么指针就有可能相互错过无法相遇。

但是我们需要的是一个一定能判断出环形链表的时候不然便会无限循环,其实我们并不太需要担心。当快慢指针分别是走一步和两步的时候是必定会相遇的。既然在同一个圈中这时就是快指针追慢指针,我们假设两指针相距N个结点,每一次循环慢指针走一步,快指针比慢指针多走了一步,那这次循环结束后两指针的距离就是N-1了,而N是一个整数就是当经过N次的循环后快指针就会追上慢指针而相遇。

慢指针走一步快指针走三步

那么当慢指针走一步,快指针走三步的时候他们是否有可能不相遇呢?

如图,这里快指针是慢指针的三倍速度,当慢指针到达循环进入结点的时候快指针就到了前一个结点了,我们再进行一个循环

快指针是直接跳过了慢指针到达慢指针的下一个结点,它们是会错过的。若这里慢指针进入循环与快指针的距离每经过一次循环距离就-2变成N-2了,若要第一圈相遇便N-2*n个循环次数为0,若最后N被减到1再经过一次循环N就变为-1了此时N为奇数,因为这时两指针的距离变为了圈的小大-1而两指针每次循环距离-2,所以圈的大小减一为偶数两指针才能相遇,即圈的大小(C)必须为奇数才能相遇。而当N为奇数第一圈不相遇的时候,C为偶数则两指针永不相遇。

这里我们将头结点到循环进入结点的距离记作L,当慢指针进入循环时快指针距离慢指针的距离记作N,圈的周长我们记作C,我们可以得到当慢指针进入循环时走的路程为L,快指针为2L,因为这个C和L都是未知的,所以我们无法判断哪个大哪个小即慢指针进入循环时可能快指针已经转了n圈了,这时快指针距离慢指针的距离是N所以我们能得出

快指针路程:3L=L+(n-1)C+(C-N)=L+nC-N:第二个公式就是快指针距离走完n圈还差N的距离

慢指针路程:L

由快指针公式我们能得出:2L=nC-N。

由上面我们得出的结论是当N为奇数而C为偶数的时候两指针永不相遇,这里我们每一项都必定是整数,我们将永不相遇的条件带入公式中,当C是偶数时n倍C必然是偶数,那么若N为奇数那么nC-N就是奇数但是2L必然也是偶数,所以两指针永不相遇的条件是不满足的。

结论:当慢指针走一步,快指针走三步的时候也是会相遇的,不是慢指针进入环后的第一圈就是第二圈。

找到环形链表中进入环形的结点

由上面的证明我们得到当使用一步两步的快慢指针找相遇点的时候

快指针:2L=L+nC-N即L=nC-N变化得nC=L+N:当我们一个指针在相遇位置即进入循环后走N然后再走L步即等于nC步即回到当前位置,若这时我们就在距离进入环得结点得N步位置即相遇位置,我们走nC-N步就是结点位置,而nC-N=L,所以我们定义两个结点一个结点在头结点位置,一个在相遇位置一起走L步这样两个都到达了进入循环点得位置了,虽然L是个未知数,但是我们知道,当头结点得指针走了L步时是第一次到达环点位置,而这时在相遇结点得指针也到达了这个位置,我们就能利用这个结论,当两指针第一次相交时就是进入环得结点得位置。代码如下

struct ListNode *detectCycle(struct ListNode *head)
{
    if(head==NULL)
    return NULL;
    struct ListNode *quick=head;
    struct ListNode *slow=head;
    
    while(quick&&quick->next)
    {
        quick=quick->next->next;
        slow=slow->next;

        if(quick==slow)
        {
            slow=head;
            while(slow!=quick)
            {
                slow=slow->next;
                quick=quick->next;
            }
            return slow;
        }
    }
    
    return NULL;
    
}

这里我们首先利用快慢指针找到两指针相遇得点,这时我们不退出循环。直接判断当两指针相遇我们使用快指针记录下相遇得点(不动它),再将慢指针重新置为头结点的地址,这时我们两指针进入循环每一次循环都向前走一步,知道两指针相遇这时我们返回相遇点(两个指针值相同随意一个都行)。这样便完成了环形链表中进入循环结点的查找。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值