带环链表与随机链表的复制——链表模块的试金石

一、引言

链表作为数据结构中的一种经典数据结构,在数据结构中有着广泛的应用,链表以结点为基本组成单元,结点为结构体类型,结构体成员中包含了指向链表下一个结点的指针,通过指针,从而将链表中的结点逐一联系起来,进而实现了链表这一经典数据结构。当然,对于链表结点的访问、插入、删除、修改等操作也都是通过链表的结点指针来实现。本文将在上述链表操作的基础上,深入研究链表的带环问题,以及实现随机链表的复制。链表带环问题与复制问题综合性强,同时也涉及了数学的相关理论知识,对链表的操作要求高,要求我们具备扎实的链表操作水平,较强的逻辑分析能力。

二、链表带环

为了更好地理解链表带环问题,我们先以单向循环链表为例

如上图所示,链表的尾结点指针指向了链表的头结点,从而实现了链表的单向循环,此时链表的头尾结点相连,链表成环。这是链表成环的一种特殊情况,由特殊到一般,当尾结点的指针指向链表的第2个,第3个结点...,尾结点的指针指向自身,链表同样也是带环链表,如下图所示,尾结点的指针指向了链表的第2个结点,该链表同样也是带环链表。

三、快慢指针

为了判断链表是否带环,我们引入了快慢指针的概念,快慢指针在链表中有着广泛的应用,快慢指针不仅可以判断链表是否带环,也可以用于求链表的中间结点。

slow慢指针一次走一个结点,fast快指针一次走2个结点,则fast走到链表尾部时,slow恰好是链表的中间结点,while循环条件fast与fast->next均不能为NULL,是因为链表的奇数个结点与偶数个结点这两种情况fast走到链表的位置不同导致的,以上是利用快慢指针来求解链表的中间结点,下面我们将利用快慢指针来判断链表是否带环。

四、判断链表是否带环

有了前面链表带环和快慢指针的认识,我们就可以利用快慢指针来对链表是否带环进行判断,它的原理为当链表带环时,则当快慢指针循环遍历该链表时,快慢两个指针在某个时刻会相遇,下面我

们来证明一下这个问题。

如上图所示,当带环链表cyclelist的slow指针刚进入该带环链表的环入口点时,假设fast指针与slow指针相差N个结点,由于fast指针一次走2个结点,slow指针一次走一个结点,则fast指针相对slow指针的速度为fast指针一次走一个结点,那么再经过N个单位时间后,fast指针将追上slow指针,两个指针会相遇,也可以参考作者下面的证明图。

综上,我们可以根据快慢指针是否相遇来对链表带环进行判断

fast与slow指针从链表的第一个结点出发,fast指针一次走2步,slow指针一次走1步,若链表带环,则fast指针与slow指针将在某个时刻相遇,反之,若链表不带环,则fast指针将变为NULL,且fast指针与slow指针不会相遇。

深度思考:当fast指针一次走3步时,情况会是怎样呢?

则此时fast指针相对slow指针的速度为2个结点,假设当slow指针进环时,fast指针与slow指针相差N个结点,这时我们需要对N的奇偶性进行讨论。

1、当N为偶数时,易知经历N/2的时间后,fast指针将追上slow指针,两指针相遇。

2、当N为奇数时,此时fast指针与slow的距离变化为N-2,N-4,N-6,...5,3,1,-1,-1即代表fast指针错过了slow指针,fast指针领先slow指针一个结点,表示fast指针第一轮没有与slow相遇,此时我们需要对环的长度进行分析,假设环的长度为C,可知此时fast与slow的距离为C-1,若C-1为偶数,由前面的分析可知,则经历(C-1)/2的时间后,fast将在第二轮追上slow,与slow相遇。若C-1为奇数,则可知fast在第二轮还会错过slow,第三轮fast与slow的距离仍为C-1,fast还是错过slow,第四轮,第五轮..也同样如此,这种情况下,fast与slow指针将不会相遇。那么是否存在N为奇数,且C-1为奇数,即C为偶数的情况呢?这时我们就需要找出N与C的数学关系。我们假设链表头结点到链表环的入口点的长度为L,假设slow指针刚进环时,fast指针已经转了x圈环,由于fast指针的速度是slow速度的3倍,则fast走过的路程也是slow走过路程的3倍,我们可以得出以下关系

L+x*C+C-N=3L,即x*C+C-N=2L,化简得(x+1)*C-N=2L,当C为偶数,则(x+1)*C必为偶数,N为奇数,则左边的代数式为偶数-奇数=奇数,而2L为偶数,则左右两边关系不成立,因此不存在N为奇数,C为偶数的情况,也就是不存在fast不能与slow相遇的情况。则fast一次走3步,slow一次走1步,两指针也会相遇,当N为偶数时,fast第一轮就与slow指针相遇,当N为奇数时,则fast指针将在第二轮与slow指针相遇。

同理,也可以分析当fast指针一次走4步,5步,6步...n步的情况,当然要分类讨论的情况自然也就变多了。

五、求带环链表的环入口点

求带环链表的环入口点,同样也是利用了快慢指针,这里我们以fast指针一次走两步,slow指针一次走一步为情况进行分析,以fast指针与slow指针相遇为起始点,假设slow指针在环内已经走了N个结点,当然slow指针不可能在环内走了第2圈,第3圈,才与fast指针相遇,因为fast指针的速度为slow指针的两倍,所以fast会在slow在环内的第1圈就追上slow,与slow相遇,同样,我们也假设fast与slow相遇,fast已经走完了x圈,由于fast的速度为slow的2倍,则fast走过的路程也为slow的路程2倍,由几何关系可得:

L+xC+N=2(L+N),化简得xC=L+N,可得(x-1)C+C-N=L,由周期性可知,L为C-N的周期倍,即一个指针从相遇点出发,另一个指针从原链表的头结点出发,两者速度一致,最终二者相遇的结点即为带环链表的环入口点。

以fast与slow指针相遇点为起始点meet,meet指针从相遇点出发,phead指针从原链表的头结点出发,通过while循环,二者相遇点即为带环链表的环入口点。

求带环链表的环入口点,也可将其转化为求两个相交链表的交点问题,以fast与slow指针相遇点为基准点meet,将meet的next指针作为新的头结点,再将meet的next指针置为NULL,就实现了将带环链表分割成两个相交链表,求带环链表的环入口点,即求这两个相交链表的相交结点,如下图所示:

这样就实现了将求带环链表的环入口点转化为求两个相交链表的相交结点的问题。

求两个链表的相交结点的方法如下:

首先,先遍历两个链表,遍历至各自的尾结点,len用于计数链表的长度,len的绝对值之差即为两个链表的长度差,利用假设法将长链表的头结点置于longlist中,短链表的头结点置于shortlist中,然后longlist指针先走差距步,之后两个指针再同时走,二者第一次相等的点即为两个链表的相交结点。

有了求两个相交链表的相交结点的方法之后,我们只需在求带环链表的环入口点的方法中调用这个函数,就求出了带环链表的环入口点。

以快慢指针的相遇点meet为起始点,将带环链表分割成两个相交链表,再调用求两个相交链表的交点这个函数,从而求出带环链表的环入口点。

六、随机链表的复制

我们来看下面这个问题

题目要求我们深拷贝一个链表,该链表与前面链表不同在于该链表多包含了一个随机指向该链表任意节点的random指针,我们深拷贝该链表的同时,也需拷贝该random指针,这是这一问题的难点与挑战所在,为了实现这个随机链表的复制,我们采取了一个较为方便,空间复杂度为O(1)的方法,具体可分为三个步骤:

1、在原链表的每个结点后插入该结点的拷贝结点

代码实现

用head1接收原链表的头结点,通过while循环,将新的拷贝结点head2逐一插入到原链表结点之后,从而建立原链表与新拷贝链表之间的联系。

2、处理新链表结点的random指针

这里需要理解好random指针的指向问题,新链表结点的random指针指向新结点的位置应与原链表相同位置的结点random指向结点的位置一致,如上图中,原链表的第二个结点的random指针指向了原链表的第一个结点的位置1,则新链表第二个结点的random指针应指向新链表第一个结点的位置,即1’,假设新链表的头结点为head3,由图中结点位置关系,我们不难得出二者random指针的关系为head3->random=head1->random->next,这一关系正是这道题的灵魂所在。

将head1指针置为原链表的头结点,通过while循环,若head1的random指针为NULL,则head3的random指针也为NULL,else则满足head3->random=head1->random->next,从而完成了新链表random指针的处理。

3、新链表尾插和原链表恢复

最后将新链表的结点进行尾插,以及对原链表结点恢复,就完成了随机链表的复制。

代码实现

将head1置为原链表的头指针,初始化新链表头,尾结点newhead,newtail为NULL,通过while循环,对新链表进行尾插,以及对原链表进行恢复,最后返回新链表头结点地址newhead,从而完成了随机链表的复制。

七、结语

至此,本文有关带环链表和随机链表的复制问题就全部介绍完了,对于带环链表,我们巧妙运用了快慢指针这个工具,快慢指针的速度差,快慢指针的必然相遇,恰恰证明了链表中环的存在,同时我们也求解了链表环入口点的相关问题,运用了转化思想,将入口点问题转化为求相交链表的交点问题。关于随机链表的复制,我们采用化繁为简的思想,分为三个步骤,原地插入,处理random指针,分离链表,从而将空间复杂度优化为O(1),很好地节省了空间。链表带环与随机链表复制问题,综合性强且对算法思想要求较高,是链表模块的试金石,如果能很好地掌握这类问题,那么对于我们链表的操作水平和思维能力都有很大的提升。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值