大家都遇到過這個問題,就是如何判斷一個單鏈表是否有環?當然,判斷方法很多,但是目前網上最出名的那個方法大概是這樣:設甲、乙兩個指標指向同一起點,之後甲乙交替著走,甲每次走兩步,乙每次走一步,遇到下面的情況則說明鏈表有環:甲某次走完兩步後遇到了乙或者乙某次走完一步後遇到了甲。那麼大家想沒想過,為什麼甲每次走兩步,乙每次走一步這樣可以?如果甲每次走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給定一個數值那麼演算法總是能夠線上性時間給出答案。
水平有限,僅能給出這樣的分析,還請大家提供更好的分析方法。