链表带环问题的方法证明 ——— 数理逻辑推理

链表带环问题的方法证明 ——— 数理逻辑推理

引言

在计算机科学中,链表是一种基本的数据结构,用于存储和组织数据。它由一系列节点组成,每个节点都包含数据和一个指向下一个节点的指针。链表的一个重要特性是它可以形成环,即链表中的一个节点通过指针指向之前的节点,从而形成一个闭合的循环结构。

链表带环问题是计算机科学中的一个经典问题,也是在实际开发中经常遇到的挑战之一。这个问题的核心在于判断一个给定的链表中是否存在环,并找到环的起始节点。虽然看起来简单,但要有效地解决这个问题需要一定的技巧和算法知识。

在本文中,我们将深入探讨链表带环问题,包括问题的定义、解决方法和数理逻辑推理。

1. 给定一个链表,判断链表中是否有环。

1.1 力扣题 —— 环形链表

在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(struct ListNode* head) {
    //定义快慢指针
    struct ListNode* fast = head;
    struct ListNode* slow = head;

    while (fast && fast->next)   //fast和fast->都不能为空
    {
        //快指针一次走两步,慢指针一次走一步。
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow)
        {
            return true;
        }
    }
    return false;
}

在这里插入图片描述
首先,我们申请两个指针,一个是快指针fast(一次走两步),一个是慢指针slow(一次走一步)。

其次,我们让两个指针向前遍历,会有两种情况。

  1. 链表不成环,那么快指针fast就会走向空NULL
  2. 链表如果成环,快指针一次走两步,慢指针一次走一步。那么两个指针的距离会越来越小直至相遇。此时证明了链表成环。

1.2 数理逻辑推理

1.2.1 第一个问题:为什么一定会相遇,有没有可能会错过,永远追不上,请证明。

在这里插入图片描述

因为快指针一次走两步,慢指针一次走一步,那么两个指针的距离会越来越小。我们设慢指针slow入环时两个指针最初距离相差N。快指针每次走两步、慢指针每次走一步:距离就变成了N-1,N-2,N-3,..…,3,2,1,0当距离相差为0时,就证明两个指针一定会相遇。


1.2.2 第二个问题:slow一次走一步,fast一次走3步,走4步,走5步,走n步,一定能追上吗?请证明。
我们设两个指针的距离相差N,圆环的长度为C。
示例slow一次走一步,fast一次走三步。
在这里插入图片描述

由此可得:如果同时存在N是奇数且C是偶数,那么fast就永远追不上。
证明
在这里插入图片描述

假设slow进环时,fast已经在环里转了x圈了,此时fastslow距离为N。

slow已经走的距离是:L
fast已经走的距离是:L + x*C + C-N 进环前的路径+绕环x周+绕环的最后一周中的已走距离

如果fast每步走的距离是slow的3倍。
3*L = L + x*C + C-N
2*L = (x+1)*C - N
偶数 = (x+1)*偶数 - 奇数

2*L肯定是偶数,如果C是偶数的话,那么他只能减偶数才能使2*L为偶数。但是,我们的假设是N必须为奇数。这是矛盾的,因此假设不成立,并反向证明了,不管fast一次走几步都可以追上。

结论:一定能追上

  1. N是偶数,第一轮就可以追上。
  2. N是奇数,第一轮追不上,C-1是偶数第二轮就能追上。

2. 给定一个链表,返回链表开始入环的第一个结点。如果链表无环,则返回NULL

2.1 力扣题——环形链表2

在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* detectCycle(struct ListNode* head) {
    struct ListNode* slow = head, * fast = head;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;

        //相遇
        if (slow == fast)
        {
            struct ListNode* meet = slow;
            while (meet != head)
            {
                meet = meet->next;
                head = head->next;
            }
            return meet;
        }
    }
    return NULL;
}

在这里插入图片描述
首先,我们申请两个指针,一个是快指针fast(一次走两步),一个是慢指针slow(一次走一步)。

其次,我们让两个指针向前遍历,当两指针相遇时,在相遇点放置一个相遇指针meet,然后通过循环,meet指针在相遇点走,head指针在头节点走,两指针相遇时,此点即为入环的第一个结点。

这时你可能有疑问,为什么一个指针在相遇点走,一个指针在头节点走,两者会在入环的第一个结点相遇。接下来就为大家证明。


2.2 数理逻辑推理

在这里插入图片描述

假设slow进环与fast相遇时,fast已经在环里转了x圈了。

slow已经走的距离是:L + N
fast已经走的距离是:L + x*C + N 进环前的路径+绕环x周+与慢指针相遇时距入环第一个结点的距离

fast走的路程是slow的2倍
2*(L + N) = L + x*C + N
L + N = x*C
L = x*C - N
L = (x-1)*C + C - N 此时x为整数且x >= 1

当x=1时,L = C - N,由此可得一个指针在相遇点走,一个指针在头节点走,两者会在入环的第一个结点相遇。

  • 40
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
在C语言中,单链表带头结点是指在链表的头部添加一个额外的结点,该结点不存储任何数据,仅用于标记链表的起始位置。引用\[3\]中的代码展示了一个带头结点的单链表的定义,其中node*表示指向一个结点的指针,linklist表示指向链表的指针。 在带头结点的单链表中,头结点的next指针指向链表的第一个实际存储数据的结点。这样做的好处是可以简化链表的操作,例如插入和删除结点时不需要特殊处理链表为空的情况。引用\[1\]和引用\[2\]中的代码展示了在带头结点的单链表中插入和删除结点的操作。 需要注意的是,在使用带头结点的单链表时,需要特别处理头结点的情况,例如在遍历链表时,通常从头结点的下一个结点开始遍历。此外,使用带头结点的单链表可以更方便地处理空链表的情况,因为头结点始终存在。 带头结点的单链表在实际应用中广泛使用,它可以更高效地进行插入、删除和查找等操作,并且可以更好地处理边界情况。 #### 引用[.reference_title] - *1* *2* *3* [带头节点的单链表(C语言版)](https://blog.csdn.net/zhu134/article/details/130632385)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值