戏谈单向链表判环
大家都遇到过这个问题,就是如何判断一个单链表是否有环?当然,判断方法很多,但是目前网上最出名的那个方法大概是这样:设甲、乙两个指针指向同一起点,之后甲乙交替着走,甲每次走两步,乙每次走一步,遇到下面的情况则说明链表有环:甲某次走完两步后遇到了乙或者乙某次走完一步后遇到了甲。那么大家想没想过,为什么甲每次走两步,乙每次走一步这样可以?如果甲每次走4步乙每次走2步可不可以?各种步长对时间复杂度有什么影响?解释这个问题需要一些点初等数论上的东西,基本上就是欧几里得和丢番图方程的知识。
插曲:丢番图方程有解的条件和解的形式 假设丢番图方程为ax+by=c,d=gcd(a,b),那么ax+by=c有解的充分必要条件为d|c。 设x0,y0是ax+by=gcd(a,b)=d的解。那么ax+by=c的解的形式为 x=x0*c/d+k*b/d y=y0*c/d-a/d k=0,1,2,......或 x=x0*c/d-k*b/d y=y0*c/d-a/d k=0,1,2,...... 插曲完毕。
画一个图,方便解释。
假如甲乙相遇时每个都走了X次,并且甲的步长是A,乙的步长是B,并且A>B。有的同学又有疑问了,为什么是都走了X次呢?为什么不能是甲走X次乙走了X-1次呢?这是因为在都走X次这种情况下,计算起来简单,并且如果我们能算出在甲乙都走了X次的情况下得时间复杂度,那么上面的判断方法的时间复杂度肯定不会超过都走X步这种情况的时间复杂度。好吧,继续往下说。
那么可以得到下面的方程X*(A-B)=Y*L2 <=> X*(A-B)-Y*L2=0,也就是说甲乙相遇时甲一定比乙多走了Y圈。把A,B,L2当成已知数。设d=gcd(A-B, L2),那么上述方程有整数解得充分必要条件为d|0,表示0能被d整除。我们发现0能被任何数整除是恒成立的。那么我们能不能推出这样的结论,任何A,B都能够判断链表是否有环呢?答案是不能:必须得去掉A=B的这种情况,因为A=B,那么甲乙每次走完肯定都会在同一个位置,那么我们是无法判断的。所以我们得出一个叫大家比较惊讶的结论:只要A,B不相等并且都大于0,那么上面的判断方法肯定能够判断链表是否有环。
那么接下来,我们要算算我们如果按照这种方法判环,时间复杂度与A,B的关系。
假设这里面A,B是已知数,L2也是已知数,X,Y是未知数。那么这个方程X*(A-B)-Y*L2=0的解是什么样子呢?
根据上面的小插曲推出它的解的形式为,设d=gcd(A-B,L2), X=x0*(0/d)+(L2/d)*k=L2/d*k Y=y0*(0/d)+((A-B)/d)*k=(A-B)/d*k k=0,1,2,...... (1) X=x0*(0/d)-(L2/d)*k=-L2/d*k Y=y0*(0/d)-((A-B)/d)*k=-(A-B)/d*k k=0,1,2,...... (2)
由实际情况可知(2)这种形式不合法,因为X,Y必须都是大于或者等于0的数。
那么这个X的最小值应该是什么样子呢,我们发现要使X最小,那么k就要尽可能的小,那么k的最小值取什么呢?k的最小值由这个不等式决定:
X*B>=L1 <=> L2/d*k*B>=L1 <=> k>=L1*d/(L2*B),那么取k=ceil(L1*d/(L2*B)),ceil代表上取整。
那么X=L2/d*k=ceil(L1*d/(L2*B))*L2/d<=(L1*d/(L2*B)+1)*L2/d=L1/B + L2/d。
我们发现,X与B有关,与A-B和L2的最大公约数有关。由我们的判断方法可知,甲乙相遇时,甲最多走X次,乙最多也走X次,而每次甲走A步,乙走B步,那么走的总步数为Z<=AX+BX=(A+B)(L1/B+L2/d)。当A=2,B=1时,d=1,那么Z<=3*(L1+L2),这里我们能够说d=1是因为A-B=1,它的最大因子就是1,而不用去理会L2到底是多少。
由Z<=AX+BX=(A+B)(L1/B+L2/d)可知,Z<=(A+B)(L1+L2)=O(L1+L2)。在这里,我们可以看到,只要A,B给定一个数值那么算法总是能够在线性时间给出答案。
水平有限,仅能给出这样的分析,还请大家提供更好的分析方法。