亀とうさぎの会議はループの存在を終わらせることを理解していますが、うさぎを待ち合わせ場所に留めたままリンクされたリストの最初に亀を移動し、続いて両方を一度に移動すると、サイクルの開始点で会うことができますか?
これは、 サイクル検出のためのフロイドのアルゴリズム です。アルゴリズムの第2フェーズについて質問しています-サイクルの一部であるノードを見つけたら、どのようにしてサイクルのstartを見つけるのでしょうか?
フロイドのアルゴリズムの最初の部分では、ウサギはカメのすべてのステップで2ステップ移動します。カメとノウサギが出会うとサイクルが発生し、ミーティングポイントはサイクルの一部になりますが、必ずしもサイクルの最初のノードではありません。
カメとノウサギが出会うと、Xが最小となる最小のi(カメがとる歩数)を見つけました。私 = X2i。 muがXから取得するステップ数を表すようにします サイクルの開始まで、ラムダがサイクルの長さを表すようにします。次に、i = mu + alambda、および2i = mu + blambdaです。ここで、aおよびbは、カメとウサギがサイクルを何回回ったかを示す整数です。最初の方程式を2番目の方程式から引くと、i =(b-a)* lambdaが得られるため、iはlambdaの整数倍になります。 したがって、Xi + mu = Xムー。バツ私 は、カメとウサギの待ち合わせ場所を表します。亀を開始ノードXに戻す場合、そしてカメとノウサギを同じ速度で続けさせます。追加のステップの後、カメはXに達します。ムー、ウサギはXに到達しますi + mu = Xムー、したがって、2番目のミーティングポイントはサイクルの開始を示します。
http://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare で提供されているサイクル検出アルゴリズムを自分の言葉で明確にしてみましょう。
仕組み
上記の図のように、リストの先頭を指すカメとウサギ(ポインターの名前)を循環させてみましょう。
カメを一度に1ステップ動かし、一度に2ステップウサギを動かすと、最終的にはカメが出会うという仮説を立てましょう。まず第一に、この仮説が正しいことを示しましょう。
図は、サイクルのあるリストを示しています。サイクルの長さはn
であり、最初はm
サイクルから離れています。また、ミーティングポイントがk
サイクルの開始から離れており、亀がi
合計ステップを踏んだときに亀と野ウサギが出会ったとしましょう。 (それまでに2i
合計ステップをとったはずです。).
次の2つの条件が満たされる必要があります。
1) i = m + p * n + k
2) 2i = m + q * n + k
最初のものは、カメがi
ステップ移動し、これらのi
ステップで最初にサイクルに到達すると言います。次に、正の数p
に対してp
回のサイクルを実行します。最後に、ウサギと出会うまでk
個以上のノードを調べます。
ウサギについても同様です。 2i
ステップ移動し、これらの2i
ステップで最初にサイクルに到達します。次に、正の数q
に対してq
回のサイクルを実行します。最後に、亀に会うまでk
個以上のノードを調べます。
うさぎはカメの2倍の速度で移動するため、両方が待ち合わせ場所に到着すると時間は一定です。
そのため、単純な速度、時間、距離の関係を使用して、
2 ( m + p * n + k ) = m + q * n + k
=> 2m + 2pn + 2k = m + nq + k
=> m + k = ( q - 2p ) n
M、n、k、p、qのうち、最初の2つは指定されたリストのプロパティです。この式を真にするk、q、pの値のセットが少なくとも1つあることを示すことができれば、仮説が正しいことを示します。
そのようなソリューションセットの1つは次のとおりです。
p = 0
q = m
k = m n - m
これらの値が次のように機能することを確認できます。
m + k = ( q - 2p ) n
=> m + mn - m = ( m - 2*0) n
=> mn = mn.
このセットでは、i
は
i = m + p n + k
=> m + 0 * n + mn - m = mn.
もちろん、これは必ずしも最小のiであるとは限りません。言い換えれば、カメとノウサギは何度も会ったことがあるかもしれません。ただし、少なくとも一度はある時点で出会うことを示しているため、仮説が正しいと言えます。そのため、1つを1ステップずつ移動し、もう1つを2ステップずつ移動すると、会議が必要になります。
これで、アルゴリズムの2番目の部分、つまりサイクルの始まりを見つける方法に進むことができます。
サイクル開始
カメとノウサギが出会ったら、リストの最初にカメを戻して、出会った場所でウサギを飼いましょう(サイクルの開始からkステップ)。
仮説は、同じ速度で移動させた場合(両方とも1ステップ)、最初に出会うのはサイクルの開始であるということです。
この仮説を証明しましょう。
最初に、Oracleがmが何であるかを教えてくれると仮定しましょう。
次に、m + kステップ移動させると、カメは最初に出会ったポイントに到着する必要があります(サイクルの開始からkステップ離れたところ-図を参照)。
以前は、m + k = (q - 2p) n
であることを示しました。
M + kステップはサイクル長nの倍数であるため、平均でhareはサイクル(q-2p)回を通過し、同じポイントに戻ります(サイクル開始からkステップ)。
さて、m + kステップ移動させる代わりに、mステップのみ移動させた場合、カメはサイクルの始めに到着します。うさぎは、(q-2p)回転を完了するのにkステップ不足です。サイクル開始の前にkステップを開始したため、ノウサギはサイクル開始に到達する必要があります。
その結果、これは、彼らが最初にいくつかのステップの後に始まるサイクルで会わなければならないことを説明します(亀はmステップ後にサイクルに到着したばかりで、すでに中のウサギを見ることはできなかったためサイクル)。
これで、それらが出会うまでに移動する必要があるステップの数は、リストの先頭からサイクルの先頭までの距離であることがわかりました。もちろん、アルゴリズムはmが何であるかを知る必要はありません。彼らは会うまで、カメと野ウサギの両方を一度に1歩ずつ動かします。ミーティングポイントはサイクルの開始点であり、ステップ数はサイクルの開始点までの距離(m)でなければなりません。リストの長さを知っていると仮定すると、リストの長さからmを引くサイクルの長さを計算することもできます。
この画像を参照してください:
会議前にslowPointerが移動した距離= x + y
会議の前にfastPointerが移動した距離=(x + y + z)+ y = x + 2y + z
FastPointerはdoubleで移動するため、slowPointerの速度、およびtimeは一定です両方がミーティングポイントに到達したとき。
したがって、単純な速度、時間、距離の関係を使用して、2(x + y)= x + 2y + z => x + 2y + z = 2x + 2y => x = z
したがって、slowPointerをリンクリストの先頭に移動し、slowPointerとfastPointerの両方を一度に1つのノードに移動させることにより、彼らは両方とも同じ距離をカバーしています.
リンクリストでループが開始するポイントに到達します。
Old Monkのシンプルで劣勢の答え は、高速ランナーが完全なサイクルを1つだけ完了したときにサイクルを見つけることを説明します。この回答では、遅いランナーがループに入る前に、速いランナーがループを複数回実行した場合について説明します。
高速ランナーが低速と高速の出会いの前にループmを実行したとしましょう。この意味は:
高速は低速の2倍の速度で実行され、同じ時間実行されているため、低速で実行された距離を2倍にすると、高速で実行された距離になります。副<文>この[前述の事実の]結果として、それ故に、従って、だから◆【同】consequently; therefore <文>このような方法で、このようにして、こんなふうに、上に述べたように◆【同】in this manner <文>そのような程度まで<文> AひいてはB◆【用法】A and thus B <文>例えば◆【同】for example; as an example、
Xを解くと、
x =(m-1)(y + z)+ z
実際のシナリオでは、x=(m-1)完全なループの実行+追加の距離z。
したがって、1つのポインターをリストの先頭に置き、もう1つをミーティングポイントに置いた場合、それらを同じ速度で移動すると、インループポインターが完了しますm-1 ループを実行し、ループの開始点でもう一方のポインターを満たします。
最初の衝突時に、カメは上記のようにm + kステップ移動しました。うさぎはカメの2倍の速さで動きます。つまり、うさぎは2(m + k)歩きます。これらの単純な事実から、次のグラフを導き出すことができます。
この時点で、カメを最初に戻し、ウサギとカメの両方が一度に1ステップずつ移動する必要があることを宣言します。定義では、mステップの後、カメはサイクルの開始点になります。うさぎはどこにいるの?
うさぎもサイクルの始まりになります。これは、2番目のグラフから明らかです。カメが最初に戻されたとき、ノウサギはk最後のサイクルに進みました。 mステップの後、ノウサギは別のサイクルを完了し、カメと衝突します。
とても簡単です。相対速度の観点から考えることができます。ウサギが2つのノードを移動し、カメが1つのノードを移動する場合、カメウサギは1つのノードを移動します(カメが休んでいると仮定します)。したがって、循環リンクリスト内の1つのノードを移動すると、必ずその時点で再び会うことになります。
循環リンクリスト内の接続ポイントを見つけた後、問題は2つのリンクリスト問題の交点を見つけることになりました。
アプローチ:
2つのポインターがあります。
2つのポインターが一致する場合、ループがあることが証明されます。一度会うと、ノードの1つが頭を指し、両方が一度に1つのノードを進めます。彼らは、ループの開始時に会います。
理由: 2人が円形のトラックを歩いているとき、1人は2倍の速度で、どこで会いますか?まさに彼らが始めた場所。
ここで、高速ランナーのk
ステップラップでn
ステップの有利なスタートがあるとします。彼らはどこで会いますか? n-k
ステップで正確に。スローランナーが(n-k)
ステップをカバーすると、ファーストランナーはk+2(n-k)
ステップをカバーします。 (ie、k+2n-2k
stepsすなわち2n-k
steps)。つまり、(n-k)
ステップ(パスは循環型であり、その後のラウンドの回数は関係ありません。単にそれらが交わる位置に関心があります)。
そもそも、高速ランナーはどのようにしてk
ステップの先頭に立つことができましたか?スローランナーがループの開始に到達するまでに多くのステップを要したからです。したがって、ループの開始点はヘッドノードからkステップです。
注:両方のポインターが出会ったノードは、k
ループの開始点(ループ内)から離れており、ヘッドノードもk
ループ開始点から離れています。したがって、これらのノードからボットから1ステップの等間隔でポインターを進めると、ループの開始時にそれらが一致します。
簡単だと思います。あいまいな部分がある場合はお知らせください。
さて、サイクルの開始からkステップ離れたポイントでウサギとカメが出会うと仮定しましょう。サイクルが始まる前のステップ数はmuで、サイクルの長さはLです。
だから今、ミーティングポイントで->
亀がカバーする距離= mu + a * L + k-式1
(サイクルの開始に到達するためのステップ+サイクルの「a」回の反復をカバーするために実行されるステップ+サイクルの開始からkステップ)(aは正の定数)
野ウサギがカバーする距離= mu + b * L + k-式2
(サイクルの開始に到達するためのステップ+サイクルの「b」回の反復をカバーするために実行されるステップ+サイクルの開始からkステップ)(bは正の定数であり、b> = a)
したがって、ノウサギによってカバーされる余分な距離は=式2-式1 =(b-a)* L
ウサギは亀の2倍の速さで動くため、この距離は開始点からの亀の距離にも等しいことに注意してください。これは、「mu + k」と同等であり、サイクルの複数のトラバーサルを含めない場合、開始点からミーティングポイントまでの距離でもあります。
したがって、mu + k =(b-a)* L
したがって、このポイントからmuステップはサイクルの開始に戻ります(サイクルの開始からkステップはすでにミーティングポイントに到達するために実行されているため)。これは、同じサイクルまたは後続のサイクルで発生する可能性があります。したがって、ここでカメをリンクリストの先頭に移動すると、サイクルの開始点に到達するまでmuステップがかかり、ウサギはサイクルの開始点に到達するまでmuステップを実行するため、両者はサイクルの開始点。
追伸正直なところ、元のポスターと同じ質問を心に抱き、最初の答えを読んで、いくつかのことを明らかにしましたが、最終結果を明確に得ることができなかったので、自分のやり方でそれを試してみました理解しやすい。
ポインターがたどるリンクの数を呼び出し、スローポインターを1リンク、ファストポインターを2リンク移動するアルゴリズムの反復回数を計ります。長さCのサイクルの前にN個のノードがあり、サイクルオフセットk = 0〜C-1でラベル付けされています。
サイクルの開始に到達するには、スローにN時間と距離がかかります。これは、サイクル内で高速でNの距離を取ることを意味します(Nはそこに到達し、Nはスピンします)。そのため、時間Nで、低速はサイクルオフセットk = 0にあり、高速はサイクルオフセットk = N mod Cにあります。
N mod Cがゼロの場合、低速と高速は一致し、サイクルは時間Nとサイクル位置k = 0で見つかります。
N mod Cがゼロでない場合、高速は低速に追いつく必要があります。これは、Nがサイクル内のC-(N mod C)の距離です。
高速は低速の1ごとに2を移動し、反復ごとに距離を1減らすため、これには時間Nの高速と低速の距離(C-(N mod C))と同じくらいの追加時間がかかります。 slowはオフセット0から移動しているため、これはそれらが出会うオフセットでもあります。
したがって、N mod Cがゼロの場合、フェーズ1はサイクルの開始時にN回の反復後に停止します。そうでない場合、フェーズ1は、サイクルへのオフセットC-(N mod C)でN + C-(N mod C)の反復後に停止します。
// C++ pseudocode, end() is one after last element.
int t = 0;
T *fast = begin();
T *slow = begin();
if (fast == end()) return [N=0,C=0];
for (;;) {
t += 1;
fast = next(fast);
if (fast == end()) return [N=(2*t-1),C=0];
fast = next(fast);
if (fast == end()) return [N=(2*t),C=0];
slow = next(slow);
if (*fast == *slow) break;
}
わかりましたので、フェーズ2:低速はサイクルに到達するためにさらにNステップを必要とし、その時点で高速(時間ステップごとに1移動)は(C-(N mod C)+ N)mod C = 0になります。フェーズ2の後のサイクルの開始時。
int N = 0;
slow = begin();
for (;;) {
if (*fast == *slow) break;
fast = next(fast);
slow = next(slow);
N += 1;
}
完全を期すために、フェーズ3では、サイクルをもう一度移動してサイクルの長さを計算します。
int C = 0;
for (;;) {
fast = next(fast);
C += 1;
if (fast == slow) break;
}
問題をループ問題に減らし、最初の問題に戻る
次の説明はより直感的です。
頭から始まる2つのポインター(1= tortoiseと2= hare)を取る(O)、1のステップ長は1、2のステップ長は2。 1がそのサイクルの開始ノード(A)に到達する瞬間を考えてください。
次の質問「1がAにあるとき2はどこですか?」に答えたいと思います。
したがって、OA = a
は自然数(a >= 0
)です。ただし、次のように記述できます:a = k*n + b
、ここでa, k, n, b are natural numbers
:
n
=サイクル長k >= 0
=定数0 <= b <= n-1
b = a % n
を意味します。
例:a = 20
およびn = 8
=> k = 2
およびb = 4
の場合20 = 2*8 + 4
であるため。
1でカバーされる距離はd = OA = a = k*n + b
です。しかし同時に、2はD = 2*d = d + d = OA + d = OA + k*n + b
をカバーします。これは、2がAにある場合、k*n + b
をカバーする必要があることを意味します。ご覧のとおり、k
はラップの数ですが、それらのラップの後、2はAからbになります。 2は1がAにあるときです。そのポイントB
を呼び出してみましょう。ここでAB = b
。
ここで、問題を円に減らします。問題は、「ミーティングポイントはどこですか?」です。それはどこC?
すべてのステップで、2は、1が1であるため、1
(メートルとしましょう) 1
で2からさらに進んでいますが、同時に2は2
によって1に近づいています。
そのため、1と2の間の距離がゼロになるとき、交差点になります。これは、2がn - b
の距離を短縮することを意味します。これを達成するために、1はn - b
ステップを作成し、2は2*(n - b)
ステップを作成します。
したがって、交点はA(時計回り)から遠く離れたn - b
になります。これは、これが1で満たされるまでの距離であるためです2。 =>CとAの間の距離は、CA = b
とAC = AB + BC = n - b
であるため、CA = n - AC
です。 AC
の距離は簡単な数学的距離ではないため、AC = CA
とは思わないでください。これは、AとC(ここでAは開始点で、Cは終了点です)。
それでは、最初のスキーマに戻りましょう。
a = k*n + b
とCA = b
を知っています。
2つの新しいポインター1 'と1' 'を使用できます。ここで、1'は先頭から始まります(O)および1 ''は交点(C)から始まります。
1 'はOからA、1' 'はCからAに進み、k
ラップを終了し続けます。したがって、交点はAです。
-ループの前にkステップあります。 kが何であるかわからないので、調べる必要はありません。 kだけで抽象的に作業できます。
-kステップ後
----- Tはサイクル開始時
----- Hはサイクル内のkステップです(合計2kになり、kがループに入ります)。
**ループサイズになりました-k離れています
(k == K == mod(loopsize、k)-ノードが5ノードサイクルに2ステップである場合、7、12、または392ステップもあることに注意してください。要因で。
一方が他方の2倍の速度で移動しているため、単位時間あたり1ステップの速度で互いに追いつくため、loopsize-kで会います。
つまり、サイクルの開始点に到達するにはk個のノードが必要であるため、ヘッドからサイクルスタートまでの距離とサイクルスタートまでの衝突は同じです。
したがって、最初の衝突の後、Tを先頭に戻します。 TとHは、それぞれ1の割合で移動すると、サイクルスタートで会います。 (両方ともkステップ)
つまり、アルゴリズムは次のとおりです。
//ループの長さを計算することにより、k = 0またはTとHがループの先頭で出会った場合に注意する
-カウンターでTまたはHを動かしてサイクルの長さを数える
-ポインターT2をリストの先頭に移動します
-サイクルステップのポインター長を移動する
-別のポインターH2を先頭に移動
-T2とH2を、サイクルの開始時に会うまでタンデムに移動します
それでおしまい!
彼らが出会うとそれが出発点になるというのは本当だとは思いません。はいループの開始時に終了します。例えば:
1->2->3->4->5->6->7->8->9->10->11->12->13->14->15->16->17->18->19->20->21->22->23->24->8
Meet at :16
Start at :8
public Node meetNodeInLoop(){
Node fast=head;
Node slow=head;
fast=fast.next.next;
slow=slow.next;
while(fast!=slow){
fast=fast.next;
fast=fast.next;
if(fast==slow) break;
slow=slow.next;
}
return fast;
}
public Node startOfLoop(Node meet){
Node slow=head;
Node fast=meet;
while(slow!=fast){
fast=fast.next;
if(slow==fast.next) break;
slow=slow.next;
}
return slow;
}
これを図で操作すると役立ちます。方程式なしで問題を説明しようとしています。
上記のすべての分析で、あなたが実例学習者である場合、私は他の誰もが説明しようとした数学を説明するのに役立つ短い分析と例を書き上げようとしました。さあ!
分析:
2つのポインターがあり、一方が他方よりも速く、一緒に移動すると、それらは最終的に再会してサイクルを示し、nullはサイクルなしを示します。
サイクルの開始点を見つけるには、...
m
は、頭からサイクルの開始までの距離です。d
はサイクル内のノードの数です。p1
は、より遅いポインターの速度です。p2
は、より速いポインターの速度です。 2は、一度に2つのノードをステップスルーすることを意味します。
次の反復を観察します。
m = 0, d = 10: p1 = 1: 0 1 2 3 4 5 6 7 8 9 10 // 0 would the start of the cycle p2 = 2: 0 2 4 6 8 10 12 14 16 18 20 m = 1, d = 10: p1 = 1: -1 0 1 2 3 4 5 6 7 8 9 p2 = 2: -1 1 3 5 7 9 11 13 15 17 19 m = 2, d = 10: p1 = 1: -2 -1 0 1 2 3 4 5 6 7 8 p2 = 2: -2 0 2 4 6 8 10 12 14 16 18
上記のサンプルデータから、高速ポインターと低速ポインターが出会うときはいつでも、サイクルの開始からm
ステップ離れていることが簡単にわかります。これを解決するには、高速のポインターを先頭に戻し、速度を低速のポインターの速度に設定します。彼らが再び会うとき、ノードはサイクルの始まりです。
相対速度 の考え方を使用した簡単な説明-物理学101 /運動学の講義。
リンクリストの開始から円の開始までの距離をx
ホップと仮定します。円の始点をポイントX
として呼び出しましょう(大文字で-上図を参照)。また、円の合計サイズをNホップと仮定します。
ノウサギの速度= 2 *カメの速度。つまり、それぞれ1 Hops/sec
と2 Hops/sec
です
カメが円X
の開始点に達すると、図のx
のポイントでウサギをさらにY
ホップする必要があります。 (ウサギはカメの2倍の距離を移動したため)。
したがって、XからYまでの時計回りの残りの弧の長さはN-x
になります。 Thisはまた、うさぎとカメが出会うことができるように、うさぎとカメの間をカバーする相対距離でもあります。この相対的な距離は、時間t_m
、つまり会う時間でカバーされるとしましょう。相対速度は(2 Hops/sec - 1 Hops/sec)
、つまり1 Hops/sec
です。したがって、相対距離=相対速度X時間を使用すると、t
= N-x
秒が得られます。そのため、カメとウサギの両方の待ち合わせ場所に到達するには、N-x
が必要です。
N-x
秒の時間と1 Hops/sec
の速度で、ポイントX
で以前にいたカメが、ミーティングポイントM
に到達するためのN-xホップをカバーします。つまり、ミーティングポイントM
はN-x
にあることを意味します。X
=から反時計回りにホップします(さらに意味します)=>ポイントx
からM
まで時計回りにX
の距離が残っています。
ただし、x
は、リンクリストの先頭からポイントX
に到達する距離でもあります。
ここで、x
のホップ数が何に対応するかは気にしません。 LinkedListの最初に1匹、亀をミーティングポイントM
に1匹置き、ホップ/ウォークさせると、必要なポイント(またはノード)であるポイントX
で会います。
まあ言ってみれば、
N[0] is the node of start of the loop,
m is the number of steps from beginning to N[0].
2つのポインターAとBがあり、Aは1倍の速度で実行され、Bは2倍の速度で実行され、両方とも最初から開始されます。
aがN [0]に達すると、BはすでにN [m]にいるはずです。 (注:Aはmステップを使用してN [0]に到達し、Bはさらにmステップである必要があります)
次に、Aはさらにステップをk回実行してBに衝突します。つまり、AはN [k]に、BはN [m + 2k]にあります(注:BはN [m]から2kステップ実行する必要があります)
AはそれぞれN [k]およびN [m + 2k]でBに衝突します。これはk = m + 2kを意味するため、k = -m
したがって、N [k]からN [0]に戻るには、さらにmステップが必要です。
簡単に言うと、コリジョンノードが見つかった後、さらにmステップ実行するだけです。最初から実行するポインタとコリジョンノードから実行するポインタを持つことができます。それらはmステップ後にN [0]で会います。
したがって、擬似コードは次のとおりです。
1) A increase 1 step per loop
2) B increase 2 steps per loop
3) if A & B are the same node, cycle found, then go to 5
4) repeat from 1
5) A reset to head
6) A increase 1 step per loop
7) B increase 1 step per loop
8) if A & B are the same node, start of the cycle found
9) repeat from 6