単一リンクリストにループがあるかどうかをどのように検出できますか?? ループがある場合、ループの開始点、つまりループが開始されたノードを見つける方法。
13 に答える
これは、リストを介して2 つのポインターを実行するだけで検出できます。このプロセスは、同じ名前の寓話にちなみ、カメとウサギのアルゴリズムとして知られています。
- まず、リストが空 (
head
isnull
) かどうかを確認します。もしそうなら、サイクルは存在しないので、今すぐやめてください。 - それ以外の場合は
tortoise
、最初のノードで最初のポインターを開始し、2 番目のノードでhead
2 番目のポインターを開始します。hare
head.next
- 次に、 (要素が 1 つのリストでは既に true である可能性があります) になる
hare
まで連続してループし、反復ごとに1 ずつ、 2 ずつ進みます。うさぎは先に出発し、より速く走るため、(端がある場合)最初に端に到達することが保証されます。null
tortoise
hare
- 終わりがない場合 (つまり、サイクルがある場合)、それらは最終的に同じノードを指し、サイクル内のどこかにノードが見つかったことを認識して停止できます。
で始まる次のループを考えてみましょう3
:
head -> 1 -> 2 -> 3 -> 4 -> 5
^ |
| V
8 <- 7 <- 6
tortoise
1 と2から始まりhare
、次の値を取ります。
(tortoise,hare) = (1,2) (2,4) (3,6) (4,8) (5,4) (6,6)
それらは で等しくなり(6,6)
、非ループ リストでは常にそれを超えるhare
必要があるため、サイクルを発見したことを意味します。tortoise
擬似コードは次のようになります。
def hasLoop (head):
return false if head = null # Empty list has no loop.
tortoise = head # tortoise initially first element.
hare = tortoise.next # Set hare to second element.
while hare != null: # Go until hare reaches end.
return false if hare.next = null # Check enough left for hare move.
hare = hare.next.next # Move hare forward two.
tortoise = tortoise.next # Move tortoise forward one.
return true if hare = tortoise # Same means loop found.
endwhile
return false # Loop exit means no loop.
enddef
このアルゴリズムの時間の複雑さはO(n)
、(カメとウサギが) 訪問したノードの数がノードの数に比例するためです。
ループ内のノードがわかれば、ループの開始点O(n)
を見つける確実な方法もあります。
ループのどこかで要素を見つけたが、ループの開始位置がわからない場合は、元の位置に戻りましょう。
head -> 1 -> 2 -> 3 -> 4 -> 5
^ |
| V
8 <- 7 <- 6
\
x (where hare and tortoise met).
これは従うべきプロセスです:
- 進め
hare
て にセットsize
し1
ます。 hare
そして、とtortoise
が異なる限り、は前進し続け、そのたびhare
に増加します。size
これにより、最終的にサイクルのサイズが得られます。この場合は 6 です。- この時点で が である場合
size
は、すでに1
サイクルの開始点にいる必要があります (サイズ 1 のサイクルでは、サイクルに存在できる可能性のあるノードは 1 つしかないため、最初のノードでなければなりません)。この場合、最初に戻って、以下の残りの手順をスキップします。hare
- それ以外の場合は、 と の両方
hare
をリストtortoise
の最初のhare
要素に設定し、正確なsize
回数 (7
この場合は まで) 進めます。これにより、サイクルのサイズだけ異なる 2 つのポインターが得られます。 hare
次に、とが異なる限り、tortoise
両方を一緒に進めます (ウサギはより落ち着いたペースで、亀と同じ速度で走っています。最初の走りで疲れているようです)。size
それらは常に互いに正確に離れた要素のままであるため、サイクルの開始に戻るのとまったく同時にサイクルの開始にtortoise
到達します。hare
次のウォークスルーでそれを確認できます。
size tortoise hare comment
---- -------- ---- -------
6 1 1 initial state
7 advance hare by six
2 8 1/7 different, so advance both together
3 3 2/8 different, so advance both together
3/3 same, so exit loop
したがって3
、 はサイクルの開始点であり、これらの操作 (サイクルの検出とサイクル開始の検出) は両方ともO(n)
連続して実行されるため、全体として もO(n)
です。
これが機能するというより正式な証拠が必要な場合は、次のリソースを調べることができます。
- 姉妹サイトに関する質問。
- ウィキペディアのサイクル検出ページ。また
- 2016 年 4 月 17 日、Peter Gammie による「The Tortoise and the Hare Algorithm」。
メソッドのサポートが必要なだけの場合 (正式な証明ではありません)、次の Python 3 プログラムを実行して、多数のサイズ (サイクル内の要素の数) とリードイン (プロセスの前の要素) の実行可能性を評価できます。サイクル開始)。
2 つのポインターが交わるポイントが常に検出されることがわかります。
def nextp(p, ld, sz):
if p == ld + sz:
return ld
return p + 1
for size in range(1,1001):
for lead in range(1001):
p1 = 0
p2 = 0
while True:
p1 = nextp(p1, lead, size)
p2 = nextp(nextp(p2, lead, size), lead, size)
if p1 == p2:
print("sz = %d, ld = %d, found = %d" % (size, lead, p1))
break
ハッシュ マップを使用して、リンク リストにループがあるかどうかを調べることもできます。以下の関数では、ハッシュ マップを使用して、リンク リストにループがあるかどうかを調べます。
static bool isListHaveALoopUsingHashMap(Link *headLink) {
map<Link*, int> tempMap;
Link * temp;
temp = headLink;
while (temp->next != NULL) {
if (tempMap.find(temp) == tempMap.end()) {
tempMap[temp] = 1;
} else {
return 0;
}
temp = temp->next;
}
return 1;
}
時間の複雑さは O(n) であるため、2 つのポインター メソッドが最適なアプローチです。
この回答は、Narasimha Karamanchi による Data structure book で読みました。
カメとウサギのアルゴリズムとも呼ばれるフロイド サイクル検出アルゴリズムを使用できます。この場合、2 つのポインターが使用されます。1 つ (たとえば) は 1 つのノードによって進められ、もう 1 つ (たとえば) は 2 つのノードによって進められます。単一のリンクされたリストにループが存在する場合、それらは必ずある時点で一致します。slowPtr
fastPtr
struct Node{
int data;
struct Node *next;
}
// program to find the begin of the loop
int detectLoopandFindBegin(struct Node *head){
struct Node *slowPtr = head, *fastPtr = head;
int loopExists = 0;
// this while loop will find if there exists a loop or not.
while(slowPtr && fastPtr && fastPtr->next){
slowPtr = slowPtr->next;
fastPtr = fastPtr->next->next;
if(slowPtr == fastPtr)
loopExists = 1;
break;
}
ループが存在する場合は、ポインターの 1 つをヘッドに向けて、両方のポインターを 1 つのノードだけ進めます。それらが出会うノードは、単一のリンクされたリストのループの開始ノードになります。
if(loopExists){
slowPtr = head;
while(slowPtr != fastPtr){
fastPtr = fastPtr->next;
slowPtr = slowPtr->next;
}
return slowPtr;
}
return NULL;
}
選択した回答を見て、いくつかの例を試してみたところ、
(A1,B1), (A2,B2) ... (AN, BN) がポインター A と B のトラバーサルであることがわかりました。
ここで、A は 1 要素をステップします。 B ステップ 2 要素、Ai と Bj は A と B が通過するノード、AN=BN。
次に、ループが開始するノードは Ak です。ここで、k = floor(N/2) です。
まったく別の方法:- リンクされたリストを逆にします。再び先頭に到達するとリストにループがあり、NULL を取得するとループはありません。総時間計算量は O(n)