単一リンクリストにループがあるかどうかを検出するにはどうすればよいですか?ループがある場合、ループの開始点、つまりループが開始されたノードを見つける方法。
リストを介してtwoポインターを実行するだけで検出できます。このプロセスは、同じ名前のf話の後に亀とウサギのアルゴリズムとして知られています。
まず、リストが空かどうかを確認します(head
はnull
です)。もしそうなら、ループは不可能なので、今すぐ停止してください。
それ以外の場合は、最初のノードtortoise
で最初のポインターhead
を開始し、2番目のノードhead.next
で2番目のポインターhare
を開始します。
次に、hare
がnull
(1要素リストで既に真である可能性がある)になるまで連続的にループし、tortoise
を1つ進め、hare
を2つ進めます各反復。うさぎは先に始まり、より速く走るので、最初に終わりに到達することが保証されています([]終わりがある場合)。
終わりがない場合(つまり、ループがある場合)、それらは最終的に同じノードを指し、ループ内でノードsomewhereが見つかったことを知って停止できます。
3
で始まる次のループを検討してください。
head -> 1 -> 2 -> 3 -> 4 -> 5
^ |
| V
8 <- 7 <- 6
tortoise
を1で、hare
を2で開始すると、次の値を取ります。
(tortoise,hare) = (1,2) (2,4) (3,6) (4,8) (5,4) (6,6)
(6,6)
で等しくなり、hare
がalwaysである必要があるため、非ループリストで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)
です。
ノードwithinループがわかれば、ループのstartを見つけるためのO(n)
保証メソッドもあります。
ループ内のどこかで要素を見つけた後、元の位置に戻りましょうが、ループの開始点が不明です。
head -> 1 -> 2 -> 3 -> 4 -> 5
^ |
| V
8 <- 7 <- 6
\
x (where hare and tortoise met).
これは従うべきプロセスです:
hare
を進め、size
を1
に設定します。hare
とtortoise
がdifferent、である限り、hare
を進め、毎回size
を増やします。これにより、最終的にループのサイズ(この場合は6)が得られます。size
が1
の場合、それは開始としてyou must *already* be at the start of the loop (in a loop of size one, there is only one possible node that can *be* in the loop so it *must* be the first in that loop). In this case, you simply return
hare`を意味し、以下の残りのステップをスキップします。hare
とtortoise
の両方をリストのfirst要素に設定し、hare
を正確にsize
回( 7
この場合)。これにより、exactlyループのサイズが異なる2つのポインターが得られます。hare
とtortoise
が異なる限り、両方を一緒に進めます(ウサギはより落ち着いたペースで、カメと同じ速度で走ります-最初から疲れていると思います)実行)。それらは常に正確にsize
要素から離れているので、tortoise
はreachループの開始exactly the hare
returnsと同じ時間でループの開始まで。次のウォークスルーでそれを確認できます。
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)
。
これが機能するというより正式な証拠が必要な場合は、次のリソースを調べることができます。
メソッドを正式にサポートした後(正式な証明ではない場合)、次の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
選択した回答は、サイクルの開始ノードを見つけるためのO(n * n)解を与えます。 O(n)ソリューション:
サイクルで遅いAと速いBが出会ったら、それらの1つを静止させ、もう1つを毎回1ステップずつ進めて、サイクルの境界、たとえばPを決定します。
次に、ノードを先頭に配置し、Pステップ移動させ、別のノードを先頭に配置します。これらの2つのノードを両方とも1ステップずつ進めます。最初に出会うと、サイクルの開始点になります。
ハッシュマップを使用して、リンクリストにループがあるかどうかを調べることもできます。以下の関数は、ハッシュマップを使用してリンクリストにループがあるかどうかを見つけます
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)ハッシュマップに必要な追加O(n)スペースの複雑さです。
ほとんどの場合、これまでの回答はすべて正しいですが、ここに視覚とコードを使用したロジックの簡略版があります(Python 3.7)
他の人が説明したように、ロジックは非常に単純です。トータス/スローとヘア/ファストを作成します。異なる速度で2つのポインターを移動すると、最終的に高速で低速になります!!これは、タック円形フィールドの2人のランナーと考えることもできます。速い走者が輪になって走り続けると、遅い走者に出会うか通過します。
したがって、インクリメントを続ける間、各反復でTortoise/slowポインターを速度1で移動するか、Hare/fastポインターを速度2で移動します。これは、 フロイドのサイクル発見アルゴリズム
以下はPythonこれを行うコードです(has_cycleメソッドが主要な部分であることに注意してください):
#!/usr/bin/env python3
class Node:
def __init__(self, data = None):
self.data = data
self.next = None
def strnode (self):
print(self.data)
class LinkedList:
def __init__(self):
self.numnodes = 0
self.head = None
def insertLast(self, data):
newnode = Node(data)
newnode.next = None
if self.head == None:
self.head = newnode
return
lnode = self.head
while lnode.next != None :
lnode = lnode.next
lnode.next = newnode # new node is now the last node
self.numnodes += 1
def has_cycle(self):
slow, fast = self.head ,self.head
while fast != None:
if fast.next != None:
fast = fast.next.next
else:
return False
slow = slow.next
if slow == fast:
print("--slow",slow.data, "fast",fast.data)
return True
return False
linkedList = LinkedList()
linkedList.insertLast("1")
linkedList.insertLast("2")
linkedList.insertLast("3")
# Create a loop for testing
linkedList.head.next.next.next = linkedList.head;
#let's check and see !
print(linkedList.has_cycle())
この答えは、Narasimha KaramanchiのData structure bookで読みました。
フロイドサイクル検索アルゴリズム、またはカメとウサギのアルゴリズムを使用できます。これでは、2つのポインターが使用されます。 1つ(たとえばslowPtr
)は1つのノードで進められ、もう1つ(たとえばfastPtr
)は2つのノードで進められます。単一のリンクリストにループが存在する場合、両方のループはある時点で確実に一致します。
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つのノードだけ進めます。それらが出会うノードは、単一のリンクリストのループのstartノードになります。
if(loopExists){
slowPtr = head;
while(slowPtr != fastPtr){
fastPtr = fastPtr->next;
slowPtr = slowPtr->next;
}
return slowPtr;
}
return NULL;
}
次のコードは、SLLにループが存在するかどうかを検出し、存在する場合はノードを返します。
int find_loop(Node *head){
Node * slow = head;
Node * fast = head;
Node * ptr1;
Node * ptr2;
int k =1, loop_found =0, i;
if(!head) return -1;
while(slow && fast && fast->next){
slow = slow->next;
/*Moving fast pointer two steps at a time */
fast = fast->next->next;
if(slow == fast){
loop_found = 1;
break;
}
}
if(loop_found){
/* We have detected a loop */
/*Let's count the number of nodes in this loop node */
ptr1 = fast;
while(ptr1 && ptr1->next != slow){
ptr1 = ptr1->next;
k++;
}
/* Now move the other pointer by K nodes */
ptr2 = head;
ptr1 = head;
for(i=0; i<k; i++){
ptr2 = ptr2->next;
}
/* Now if we move ptr1 and ptr2 with same speed they will meet at start of loop */
while(ptr1 != ptr2){
ptr1 = ptr1->next;
ptr2 = ptr2->next;
}
return ptr1->data;
}
選択した答えを見て、いくつかの例を試したところ、次のことがわかりました。
(A1、B1)、(A2、B2)...(AN、BN)がポインターAおよびBのトラバースである場合
ここで、Aステップ1要素とBステップ2要素、およびAiとBjは、AとBが横断するノードであり、AN = BNです。
次に、ループの開始ノードはAkで、k = floor(N/2)です。
別の解決策
ループの検出:
ループの削除:
ステップ#3でループを検出したら、前のノードの次の値をNULLに設定します
#コード
def detect_remove_loop(head)
cur_node = head
node_list = []
while cur_node.next is not None:
prev_node = cur_node
cur_node = cur_node.next
if cur_node not in node_list:
node_list.append(cur_node)
else:
print('Loop Detected')
prev_node.next = None
return
print('No Loop detected')
わかりました-昨日のインタビューでこれに遭遇しました-参考資料がなく、非常に異なる答えを思い付きました(もちろん家に帰るとき...)リンクされたリストは通常(必ずしも認めるわけではない)mallocロジックを使用して割り当てられている割り当ての粒度がわかっていることがわかります。ほとんどのシステムでは、これは8バイトです-これは、下位3ビットが常にゼロであることを意味します。考慮-リンクリストをアクセスを制御するクラスに配置し、0x0Eのマスクを次のアドレスに使用する場合、下位3ビットを使用してブレーククラムを格納できます。したがって、最後のパンくずを格納するメソッドを記述できます-1または2と言います-そしてそれらを交互にします。ループをチェックするメソッドは、各ノードをステップ実行し(次のメソッドを使用)、次のアドレスに現在のブレッドクラムが含まれているかどうかを確認します-ループがある場合-含まれていない場合、下位3ビットをマスクします現在のブレッドクラムを追加します。パンくずリストのチェックアルゴリズムは、一度に2つを実行することはできませんが、他のスレッドがリストの非同期にアクセスできるようにするため、シングルスレッドにする必要があります-ノードの追加/削除に関する通常の注意事項どう思いますか?他の人がこれが有効な解決策であると感じたら、サンプルクラスを書き上げることができます...たまに新鮮なアプローチが良いと思います。
まず、ノードを作成します
struct Node {
int data;
struct Node* next;
};
ヘッドポインターをグローバルに初期化する
Struct Node* head = NULL;
リンクリストにデータを挿入する
void insert(int newdata){
Node* newNode = new Node();
newNode->data = newdata;
newNode->next = head;
head = newNode;
}
関数detectLoop()を作成します
void detectLoop(){
if (head == NULL || head->next == NULL){
cout<< "\nNo Lopp Found in Linked List";
}
else{
Node* slow = head;
Node* fast = head->next;
while((fast && fast->next) && fast != NULL){
if(fast == slow){
cout<<"Loop Found";
break;
}
fast = fast->next->next;
slow = slow->next;
}
if(fast->next == NULL){
cout<<"Not Found";
}
}
}
Main()から関数を呼び出す
int main()
{
insert(4);
insert(3);
insert(2);
insert(1);
//Created a Loop for Testing, Comment the next line to check the unloop linkedlist
head->next->next->next->next = head->next;
detectLoop();
//If you uncomment the display function and make a loop in linked list and then run the code you will find infinite loop
//display();
}
boolean hasLoop(Node *head)
{
Node *current = head;
Node *check = null;
int firstPtr = 0;
int secondPtr = 2;
do {
if (check == current) return true;
if (firstPtr >= secondPtr){
check = current;
firstPtr = 0;
secondPtr= 2*secondPtr;
}
firstPtr ++;
} while (current = current->next());
return false;
}
別のO(n)ソリューション。
bool FindLoop(struct node *head)
{
struct node *current1,*current2;
current1=head;
current2=head;
while(current1!=NULL && current2!= NULL && current2->next!= NULL)
{
current1=current1->next;
current2=current2->next->next;
if(current1==current2)
{
return true;
}
}
return false;
}