web-dev-qa-db-ja.com

リンクされたリストでループを見つけている間、なぜポインタを2増やします、なぜ3,4,5ではありませんか?

question を見て、リンクされたリストでループを見つけるアルゴリズムについて話しました。私は フロイドのサイクル探索アルゴリズム ソリューションを読みました。2つのポインターをとらなければならない多くの場所で言及されています。 1つのポインター(遅い/亀)が1つ増加し、他のポインター(速い/ウサギ)が2増加します。これらが等しい場合、ループが見つかり、高速なポインターがnullに到達すると、リンクリストにループがありません。

さて、私の質問は、なぜポインタを2倍にするのかということです。結果を得るには、2ずつ増やすか、Xずつ増やすことができます。より速いポインターを2ずつインクリメントする場合、または3または5またはxをインクリメントする必要がある場合がある場合に、ループを見つける必要がありますか。

53
GG.

番号2を使用する必要がある理由はありません。ステップサイズは任意に選択できます(もちろん1つを除く)。

これが機能する理由を理解するために、まずフロイドのアルゴリズムが機能する理由を見てみましょう。アイデアはシーケンスxについて考えることです、 バツ1、 バツ2、 ...、 バツ、...リンクリストの要素のうち、リストの先頭から始めて、最後に到達するまでそれを下に移動し続けるとアクセスする要素のリストです。リストにサイクルが含まれていない場合、これらの値はすべて異なります。ただし、サイクルが含まれている場合、このシーケンスは無限に繰り返されます。

フロイドのアルゴリズムを機能させる定理は次のとおりです。

リンクリストには、正の整数jが存在し、正の整数kに対してxが存在する場合にのみ、循環が含まれます。j = xjk

これを証明してみましょう。それほど難しいことではありません。 「if」の場合、そのようなjが存在する場合、k = 2を選択します。次に、いくつかの正のj、xj = x2j とj≠2jなので、リストには循環が含まれます。

他の方向については、リストに位置sから始まる長さlのサイクルが含まれていると仮定します。 jをsよりも大きいlの最小の倍数とする。次に、任意のkについて、xを考慮するとj そしてxjk、jはループ長の倍数なので、xを考えることができますjk リストの位置jから始まり、jステップk-1回実行することによって形成される要素として。しかし、これらの各回jステップを実行すると、jはループ長の倍数であるため、リストの最初の位置に戻ります。したがって、xj = xjk

この証明により、各反復で一定数のステップを実行した場合、実際にスローポインターにヒットすることが保証されます。より正確には、各反復でkステップを取る場合、最終的に点xを見つけます。j そしてxkj サイクルを検出します。直感的に、各反復のステップ数が最も少ないため、ランタイムを最小化するためにk = 2を選択する傾向があります。

次のように、ランタイムをより正式に分析できます。リストにサイクルが含まれていない場合、高速ポインタはO(n)時間のnステップ後にnがリスト内の要素の数です。そうでない場合、スローポインターがjステップを実行した後に2つのポインターが出会います。jはsより大きいlの最小の倍数であることを覚えておいてください。s≤lの場合、j = l、それ以外の場合、s> lの場合、jほとんどの2s、したがってjの値はO(s + l)です。lとsはリスト内の要素の数以下にすることができるため、これはj = O(n)よりも意味します。ただし、スローポインターの後jステップを実行した場合、高速ポインターは低速ポインターが実行したjステップごとにkステップを実行するため、O(kj)ステップを実行します。j= O(n )、ネットランタイムは最大でO(nk)です。これは、高速ポインタで実行するステップが多いほど、アルゴリズムが完了するまでに時間がかかることを示しています(比例的にのみです)。したがって、k = 2を選択すると、全体が最小化されます。アルゴリズムのランタイム。

お役に立てれば!

50
templatetypedef

ループを含まないリストの長さがsで、ループの長さがtであり、_fast_pointer_speed_と_slow_pointer_speed_の比率がkであるとします。

ループの開始からjの距離で2つのポインターが出会うようにします。

したがって、スローポインターが移動する距離= _s + j_。高速ポインターの移動距離= _s + j + m * t_(ここで、mは、高速ポインターがループを完了した回数です)。ただし、高速ポインタもk * (s + j)k×低速ポインタの距離)の距離を移動します。

したがって、k * (s + j) = s + j + m * tを取得します。

s + j = (m / k-1)t

したがって、上記の式から、スローポインターが移動する長さはループ長の整数倍になります。

最大の効率を得るには、_(m / k-1) = 1_(遅いポインターがループを2回以上移動するべきではありません。)

したがって、_m = k - 1 => k = m + 1_

mは高速ポインタがループを完了した回数なので、_m >= 1_です。最大の効率を得るには、_m = 1_。

したがって、_k = 2_。

_k > 2_の値を取る場合、2つのポインターが移動する必要がある距離が長くなります。

上記の説明がお役に立てば幸いです。

32
Sumit Das

サイズLのサイクルを考えます。これは、k番目の要素でループがある場所を意味します。xk -> xk + 1 -> ...-> xk + L-1 -> xk。 1つのポインターがレートrで実行されているとします。1= 1とその他のr2。最初のポインタがxに達したときk 2番目のポインタは、すでに要素xのループにありますk + s ここで、0 <= s <Lです。mがさらにポインタを増分すると、最初のポインタはxにあります。k +(m mod L) そして2番目のポインタはxにありますk +((m * r2+ s)mod L)。したがって、2つのポインタが衝突する条件は、合同を満たすmの存在として表現できます。

m = m * r2 + s(mod L)

これは次の手順で簡略化できます

m(1-r2)= s(mod L)

m(L + 1-r2)= s(mod L)

これは線形合同の形式です。 sがgcd(L + 1-r)で割り切れる場合、解はmになります。2、L)。これは確かにgcd(L + 1-r2、L)= 1。もしr2= 2次にgcd(L + 1-r2、L)= gcd(L-1、L)= 1であり、解mは常に存在します。

したがって、r2= 2は、任意のサイクルサイズLで、gcd(L + 1-r2、L)= 1なので、2つのポインターが異なる場所で開始された場合でも、最終的にポインターが衝突することが保証されます。 rの他の値2 このプロパティはありません。

6
user782220

高速ポインターが_3_ステップで移動し、低速ポインターが_1_ステップで移動する場合、偶数のノードを含むサイクルで両方のポインターが一致することは保証されません。ただし、スローポインターが_2_ステップで移動した場合、会議は保証されます。

一般に、うさぎがHステップで移動し、亀がTステップで移動する場合、_H = T + 1_の場合にサイクルで出会うことが保証されます。

亀と相対的に動くうさぎを考えてみましょう。

  • 亀に対する相対的なうさぎの速度は、反復あたり_H - T_ノードです。
  • 長さN =(H - T) * kのサイクルが与えられ、kは任意の正の整数である場合、ノウサギはすべての_H - T - 1_ノードをスキップします(これも亀に対して)、これは不可能です。亀がそれらのノードのいずれかにある場合、彼らが会うために。

  • 会議が保証される唯一の可能性は、_H - T - 1 = 0_の場合です。

したがって、低速ポインタが_x - 1_によって増加されている限り、xによって高速ポインタを増加させることができます。

2
John Bupit

(nノードの)ループがある場合、ポインターがループに入ると、それは永久にそこに残ります。両方のポインタがループに入るまで、時間を先に進めることができます。ここから、ポインタは、初期値aおよびbを持つnを法とする整数で表すことができます。彼らがtステップ後に満たすべき条件は、

a +t≡b+ 2t mod nで、解はt = a−b mod nです。

これは、速度の違いがnと素因数を共有しない限り機能します。

リファレンス https://math.stackexchange.com/questions/412876/proof-of-the-2-pointer-method-for-finding-a-linked-list-loop

速度に対する単一の制限は、それらの違いがループの長さと同じになることです。

0
Peter