web-dev-qa-db-ja.com

単一リンクリストでループを見つける

単一リンクリストにループがあるかどうかを検出するにはどうすればよいですか?ループがある場合、ループの開始点、つまりループが開始されたノードを見つける方法。

59
Jainendra

リストを介してtwoポインターを実行するだけで検出できます。このプロセスは、同じ名前のf話の後に亀とウサギのアルゴリズムとして知られています。

まず、リストが空かどうかを確認します(headnullです)。もしそうなら、ループは不可能なので、今すぐ停止してください。

それ以外の場合は、最初のノードtortoiseで最初のポインターheadを開始し、2番目のノードhead.nextで2番目のポインターhareを開始します。

次に、harenull(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)で等しくなり、harealwaysである必要があるため、非ループリストで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を進め、size1に設定します。
  • その後、haretortoisedifferent、である限り、hareを進め、毎回sizeを増やします。これにより、最終的にループのサイズ(この場合は6)が得られます。
  • この時点で、size1の場合、それは開始として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`を意味し、以下の残りのステップをスキップします。
  • それ以外の場合は、haretortoiseの両方をリストのfirst要素に設定し、hareを正確にsize回( 7この場合)。これにより、exactlyループのサイズが異なる2つのポインターが得られます。
  • 次に、haretortoiseが異なる限り、両方を一緒に進めます(ウサギはより落ち着いたペースで、カメと同じ速度で走ります-最初から疲れていると思います)実行)。それらは常に正確にsize要素から離れているので、tortoisereachループの開始exactly the harereturnsと同じ時間でループの開始まで。

次のウォークスルーでそれを確認できます。

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
124
paxdiablo

選択した回答は、サイクルの開始ノードを見つけるためのO(n * n)解を与えます。 O(n)ソリューション:

サイクルで遅いAと速いBが出会ったら、それらの1つを静止させ、もう1つを毎回1ステップずつ進めて、サイクルの境界、たとえばPを決定します。

次に、ノードを先頭に配置し、Pステップ移動させ、別のノードを先頭に配置します。これらの2つのノードを両方とも1ステップずつ進めます。最初に出会うと、サイクルの開始点になります。

35
Dongliang Yu

ハッシュマップを使用して、リンクリストにループがあるかどうかを調べることもできます。以下の関数は、ハッシュマップを使用してリンクリストにループがあるかどうかを見つけます

    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)スペースの複雑さです。

6

ほとんどの場合、これまでの回答はすべて正しいですが、ここに視覚とコードを使用したロジックの簡略版があります(Python 3.7)

他の人が説明したように、ロジックは非常に単純です。トータス/スローとヘア/ファストを作成します。異なる速度で2つのポインターを移動すると、最終的に高速で低速になります!!これは、タック円形フィールドの2人のランナーと考え​​ることもできます。速い走者が輪になって走り続けると、遅い走者に出会うか通過します。

したがって、インクリメントを続ける間、各反復でTortoise/slowポインターを速度1で移動するか、Hare/fastポインターを速度2で移動します。これは、 フロイドのサイクル発見アルゴリズムenter image description here

以下は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())
2
grepit

この答えは、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;
        }
2
Ayush Chaurasia

次のコードは、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;

}
1
jitsceait

選択した答えを見て、いくつかの例を試したところ、次のことがわかりました。
(A1、B1)、(A2、B2)...(AN、BN)がポインターAおよびBのトラバースである場合
ここで、Aステップ1要素とBステップ2要素、およびAiとBjは、AとBが横断するノードであり、AN = BNです。
次に、ループの開始ノードはAkで、k = floor(N/2)です。

0
Be Wake Pandey

別の解決策

ループの検出:

  1. リストを作成する
  2. linkedlistをループして、ノードをリストに追加し続けます。
  3. Nodeがリストにすでに存在する場合、ループが発生しています。

ループの削除:

  1. 上記の手順2では、リンクリストをループしながら、前のノードも追跡します。
  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')
    
0
Santosh Pillai

わかりました-昨日のインタビューでこれに遭遇しました-参考資料がなく、非常に異なる答えを思い付きました(もちろん家に帰るとき...)リンクされたリストは通常​​(必ずしも認めるわけではない)mallocロジックを使用して割り当てられている割り当ての粒度がわかっていることがわかります。ほとんどのシステムでは、これは8バイトです-これは、下位3ビットが常にゼロであることを意味します。考慮-リンクリストをアクセスを制御するクラスに配置し、0x0Eのマスクを次のアドレスに使用する場合、下位3ビットを使用してブレーククラムを格納できます。したがって、最後のパンくずを格納するメソッドを記述できます-1または2と言います-そしてそれらを交互にします。ループをチェックするメソッドは、各ノードをステップ実行し(次のメソッドを使用)、次のアドレスに現在のブレッドクラムが含まれているかどうかを確認します-ループがある場合-含まれていない場合、下位3ビットをマスクします現在のブレッドクラムを追加します。パンくずリストのチェックアルゴリズムは、一度に2つを実行することはできませんが、他のスレッドがリストの非同期にアクセスできるようにするため、シングルスレッドにする必要があります-ノードの追加/削除に関する通常の注意事項どう思いますか?他の人がこれが有効な解決策であると感じたら、サンプルクラスを書き上げることができます...たまに新鮮なアプローチが良いと思います。

0
Mark Z. Kumler

まず、ノードを作成します

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();
} 
0
Inamur Rahman
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)ソリューション。

0
Apurva Sharma
                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;
                }