web-dev-qa-db-ja.com

単一リンクリンクリストでループの開始を検出しますか?

2つ以下のポインター)を使用してリンクリスト内のループの開始を見つける方法はありますか?すべてのノードにアクセスして、それを表示済みとしてマークし、最初のノードが既に表示されたことを報告したくない。これを行う他の方法はありますか?

25
Biswajyoti Das

この正確な質問を面接クイズの質問として聞いたことがあります。

最もエレガントな解決策は次のとおりです。

最初の要素に両方のポインターを置きます(それらをAおよびBと呼びます)

次に、ループを続けます::

  • Aを次の要素に進めます
  • Aを次の要素に再度進めます
  • Bを次の要素に進めます

2つのポインターがそれを指している要素を実際に見つけたい場合、それはより困難です。リンクリストを何度もたどるつもりがない限り、私は手足から出て、2つのポインタだけで行うことは不可能だと言います。

より多くのメモリを使用してこれを行う最も効率的な方法は、要素へのポインタを配列に配置し、並べ替えてから、繰り返しを探すことです。

0
Matthias Wandel

Step1:通常の方法で続行し、ループを見つけるために使用します。つまり、2つのポインターを持ち、1つをシングルステップでインクリメントし、もう1つを2ステップでインクリメントします。両方がいつか出会う場合、ループがあります。 。

Step2: 1つのポインターを元の場所でフリーズし、もう1つのポインターを1ステップでインクリメントして、実行したステップをカウントします。両方が再び出会うと、カウントによってループの長さがわかります(これは循環リンクリスト内の要素の数を数える)。

Step3:両方のポインタをリンクリストの先頭にリセットし、1つのポインタをループ時間の長さにインクリメントしてから、2番目のポインタを開始します。両方のポインターを1つのステップでインクリメントし、それらが再び出会うと、ループの開始になります(これは、nを見つけることと同じです。th リンクリストの最後からの要素)。

122
balki

数学的な証明+解決策

Let 'k' be the number of steps from HEADER to BEGINLOOP.
Let 'm' be the number of steps from HEADER to MEETPOINT.
Let 'n' be the number of steps in the loop.
Also, consider two pointers 'P' and 'Q'. Q having 2x speed than P.

単純なケース:k <Nの場合

ポインタ「P」がBEGINLOOPにある場合(つまり、「k」ステップ移動した場合)、Qは「2k」ステップ移動します。したがって、事実上、Pがループに入るときQはPから「2k-k = k」ステップ進んでいます。したがって、QはBEGINLOOPより「n-k」ステップ遅れています。

PがBEGINLOOPからMEETPONTに移動した場合、Pは「m-k」ステップを移動します。その時、Qは「2(m-k)」ステップを移動したでしょう。しかし、彼らが出会い、QがBEGINLOOPの後ろに「n-k」ステップを開始したので、事実上、「2(m-k)n-k)」は「(m-k)」と等しくなるはずです。

=> 2m - 2k - n + k = m - k
=> 2m - n = m
=> n = m

つまり、PとQは、ループ内のステップ数(または、一般的には複数、以下のケースを参照)に等しいポイントで交わります。ここで、MEETPOINTでは、n = mであるように、PとQの両方が「n-(m-k)」ステップ遅れています。つまり、「k」ステップ遅れています。したがって、再びHEADERからPを開始し、MEETPOINTからQを開始したが、今回はPに等しいペースで、PとQはBEGINLOOPでのみ会議になります。

---(一般的なケース:言う、k = nX + Y、Y <n(したがって、k%n = Y)

ポインタ「P」がBEGINLOOPにある場合(つまり、「k」ステップ移動した場合)、Qは「2k」ステップ移動します。したがって、事実上、Pがループに入るとき、QはPから「2k-k = k」ステップよりも進んでいます。ただし、「k」は「n」よりも大きいことに注意してください。これは、Qがループを複数回実行したことを意味します。したがって、事実上、QはBEGINLOOPから「n-(k%n)」ステップ遅れています。

PがBEGINLOOPからMEETPOINTに移動した場合、「m-k」ステップ移動します。 (したがって、事実上、MEETPOINTはBEGINLOOPよりも '(m-k)%n'ステップ進んでいます。)そのとき、Qは '2(m-k)'ステップを移動していました。しかし、彼らが出会い、QがBEGINLOOPの後ろに 'n-(k%n)'ステップを開始したので、事実上、Qの新しい位置(つまり '(2(mk)-(nk /%n))%n 'BEGINLOOPからの)は、Pの新しい位置(BEGIN LOOPからの'(mk)%n ')と等しくなければなりません。

そう、

=> (2(m - k) - (n - k%n))%n = (m - k)%n
=> (2(m - k) - (n - k%n))%n = m%n - k%n
=> (2(m - k) - (n - Y))%n = m%n - Y   (as k%n = Y)
=> 2m%n - 2k%n - n%n + Y%n = m%n - Y
=> 2m%n - Y - 0 + Y = m%n - Y    (Y%n = Y as Y < n)
=> m%n = 0
=> 'm' should be multiple of 'n'
28
pikoooz

まず、リストにループがあるかどうかを調べます。ループが存在する場合は、ループの開始点を見つけようとします。このために、slowPtrとfastPtrという2つのポインターを使用します。最初の検出(ループのチェック)では、fastPtrは一度に2ステップ移動しますが、slowPtrは一度に1ステップ進みます。

enter image description here

slowPtr   1   2   3   4   5   6   7
fastPtr   1   3   5   7   9   5   7

FastPtrポインターは他のポインターよりも2倍速く実行されているため、リストにループがある場合、それらはポイント(上の画像のポイント7)で出会うことは明らかです。

ここで、ループの開始点を見つけるという2番目の問題が発生します。

彼らがポイント7で会うと仮定します(上の画像で述べたように)。次に、slowPtrはループから抜け出し、ポイント1でリスト平均の先頭に立っていますが、fastPtrはまだミーティングポイント(ポイント7)にあります。ここで、両方のポインター値を比較します。同じ場合はループの開始点です。それ以外の場合は、1ステップ先に移動し(ここでは、fastPtrも毎回1ステップ移動します)、同じポイントが見つかるまで再度比較します。

slowPtr   1   2   3   4
fastPtr   7   8   9   4

ここで1つの質問が思い浮かびます。それは、どのようにして可能になるのかということです。したがって、優れた数学的証明があります。

仮定:

m => length from starting of list to starting of loop (i.e 1-2-3-4)
l => length of loop (i.e. 4-5-6-7-8-9)
k => length between starting of loop to meeting point (i.e. 4-5-6-7)

Total distance traveled by slowPtr = m + p(l) +k
where p => number of repetition of circle covered by slowPtr

Total distance traveled by fastPtr = m + q(l) + k
where q => number of repetition of circle covered by fastPtr

Since,
fastPtr running twice faster than slowPtr

Hence,
Total distance traveled by fastPtr = 2 X Total distance traveled by slowPtr
i.e
m + q(l) + k = 2 * ( m + p(l) +k )
or, m + k = q(l) - p(l)
or, m + k = (q-p) l
or, m = (q-p) l - k

So,
If slowPtr starts from beginning of list and travels "m" length then, it will reach to Point 4 (i.e. 1-2-3-4)

and
fastPtr start from Point 7 and travels " (q-p) l - k " length then, it will reach to Point 4 (i.e. 7-8-9-4),
because "(q-p) l" is a complete circle length with " (q-p) " times.

詳細はこちら

12

これは、リンクリストでループの開始を見つけるためのコードです。

public static void findStartOfLoop(Node n) {

    Node fast, slow;
    fast = slow = n;
    do {
        fast = fast.next.next;
        slow = slow.next;
    } while (fast != slow);       

    fast = n;
    do {
        fast = fast.next;
        slow = slow.next;
    }while (fast != slow);

    System.out.println(" Start of Loop : " + fast.v);
}
4
user3068662

ループを見つけるために使用する通常の方法で続行します。すなわち。 2つのポインターがあり、1つをシングルステップ(slowPointer)で、もう1つを2ステップ(fastPointer)でインクリメントします。両方がいつか出会うと、ループが発生します。

すでにお気づきかもしれませんが、ミーティングポイントはループの先頭のkステップ前です。

ここで、kはリストのループされていない部分のサイズです。

ループの先頭にゆっくり移動します

衝突点で高速に保つ

それらのそれぞれは、ループの開始からkステップです(リストの最初から遅いですが、ループの先頭の前のkステップが速いです-明確にするために写真を描いてください)

今度はそれらを同じ速度で動かします-それらはループ開始時に会わなければなりません

例えば

slow=head
while (slow!=fast)
{
     slow=slow.next;
     fast=fast.next;
}
4
Manish Ranjan

包括的な回答については、 this リンクを参照してください。

1
Atul Sureka

リンクリストでループを見つける方法は2つあります。 1.ループがある場合は、2つのポインターを1つ進め、もう1つは2ステップ進めます。ある時点で、両方のポインターが同じ値を取得し、nullに到達することはありません。ただし、ループポインタがない場合、1つのポイントでnullに到達し、両方のポインタが同じ値を取得することはありません。しかし、このアプローチでは、リンクリストにループがあることはわかりますが、ループの開始位置を正確に知ることはできません。これも効率的な方法ではありません。

  1. 値が一意になるようにハッシュ関数を使用します。複製を挿入しようとしている場合は、例外を通過する必要があります。次に、各ノードを移動し、アドレスをハッシュにプッシュします。ポインタがnullに到達し、ハッシュからの例外がない場合は、リンクリストにサイクルがないことを意味します。ハッシュから例外が発生した場合は、リストにサイクルがあり、それがサイクルの開始元のリンクであることを意味します。
1
SHANAVAS P

私が見つけた最良の答えはここにありました:
tianrunhe:find-loop-starting-point-in-a-circular-linked-list

  • 'm'はHEADとSTART_LOOPの間の距離です
  • 「L」はループ長です
  • 「d」はMEETING_POINTとSTART_LOOPの間の距離です
  • p1はVで移動し、p2は2 * Vで移動します

    2つのポインターが出会うとき:走行距離は= m + n * L -d = 2 *(m + L -d)

    =>これは、p1がHEAD&p2がMEETING_POINTから始まり、同じペースで移動する場合、@ START_LOOPに一致することを意味します(ここでは数学的に示されていません)。

1
Remi Thieblin

さて、1つのポインタを使用して方法を試しました...複数のデータセットでメソッドを試しました...リンクリストの各ノードのメモリは昇順で割り当てられるため、リンクリストの先頭で、ノードのアドレスがそれが指しているノードのアドレスよりも大きくなった場合、ループとループの開始要素があると判断できます。

1
Anirban Datta
void loopstartpoint(Node head){
    Node slow = head.next;;
    Node fast = head.next.next;

    while(fast!=null && fast.next!=null){
        slow = slow.next;
        fast = fast.next.next;

        if(slow==fast){
            System.out.println("Detected loop ");
            break;
        }
    }

    slow=head;
    while(slow!=fast){
        slow= slow.next;
        fast = fast.next;
    }
    System.out.println("Starting position of loop is "+slow.data);
}
0
josepainumkal
  1. ループを見つけるために使用する通常の方法で続行します。すなわち。 2つのポインターがあり、1つはシングルステップで、もう1つは2ステップでインクリメントします。両方がいつか出会うと、ループが発生します。

  2. ポインタの1つを固定したままにして、ループ内のノードの総数(Lなど)を取得します。

  3. ここで、ループ内のこのポイント(ループ内の次のノードへの2番目のポインターをインクリメント)から、リンクリストを逆にして、通過したノードの数、たとえばXをカウントします。

  4. ここで、ループ内の同じポイントから2番目のポインター(ループが壊れている)を使用して、リンクリストを移動し、残りのノードの数、たとえばYをカウントします。

  5. ループは((X + Y)-L)\ 2ノードの後に​​始まります。または、(((X + Y)-L)\ 2 + 1)番目のノードから開始します。

0
bincy mani
  1. ループを見つけるために使用する通常の方法で続行します。すなわち。 2つのポインターがあり、1つはシングルステップで、もう1つは2ステップでインクリメントします。両方がいつか出会うと、ループが発生します。

  2. ポインタの1つを固定したままにして、ループ内のノードの総数(Lなど)を取得します。

  3. ここで、ループ内のこのポイント(ループ内の次のノードへの2番目のポインターをインクリメント)から、リンクリストを逆にして、通過したノードの数、たとえばXをカウントします。

  4. ここで、ループ内の同じポイントから2番目のポインター(ループが壊れている)を使用して、リンクリストを移動し、残りのノードの数、たとえばYをカウントします。

  5. ループは((X + Y)-L)\ 2ノードの後に​​始まります。または、(((X + Y)-L)\ 2 + 1)番目のノードから開始します。

0
bincy mani