it-swarm.com.de

Wie erkennt man eine Schleife in einer verknüpften Liste?

Angenommen, Sie haben eine verknüpfte Listenstruktur in Java. Es besteht aus Knoten:

class Node {
    Node next;
    // some user data
}

und jeder Knoten zeigt auf den nächsten Knoten, mit Ausnahme des letzten Knotens, der für den nächsten Nullpunkt hat. Angenommen, es gibt eine Möglichkeit, dass die Liste eine Schleife enthalten kann - d. H. Der letzte Knoten hat anstelle einer Null eine Referenz auf einen der Knoten in der Liste, die davor gekommen sind.

Was ist die beste Schreibweise?

boolean hasLoop(Node first)

was würde true zurückgeben, wenn der angegebene Knoten der erste einer Liste mit einer Schleife ist, andernfalls false? Wie können Sie so schreiben, dass es einen konstanten Platz und eine angemessene Zeit erfordert?

Hier ist ein Bild davon, wie eine Liste mit einer Schleife aussieht:

alt text

396
jjujuma

Sie können den Floyd-Zyklusfindungsalgorithmus verwenden, der auch als Tortoise and Hare-Algorithmus bekannt ist.

Die Idee ist, zwei Verweise auf die Liste zu haben und sie mit verschiedenen Geschwindigkeiten zu verschieben. Bewegen Sie einen vorwärts um den 1-Knoten und den anderen um die 2-Knoten. 

  • Wenn die verknüpfte Liste eine Schleife hat, werden sie Definitiv treffen.
  • Anderenfalls werden entweder die beiden Referenzen (oder ihre next) null.

Java-Funktion zur Implementierung des Algorithmus:

boolean hasLoop(Node first) {

    if(first == null) // list does not exist..so no loop either
        return false;

    Node slow, fast; // create two references.

    slow = fast = first; // make both refer to the start of the list

    while(true) {

        slow = slow.next;          // 1 hop

        if(fast.next != null)
            fast = fast.next.next; // 2 Hops
        else
            return false;          // next node null => no loop

        if(slow == null || fast == null) // if either hits null..no loop
            return false;

        if(slow == fast) // if the two ever meet...we must have a loop
            return true;
    }
}
507
codaddict

Hier ist eine Verbesserung der Fast/Slow-Lösung, die ungerade Längenlisten korrekt verarbeitet und die Übersichtlichkeit verbessert.

boolean hasLoop(Node first) {
    Node slow = first;
    Node fast = first;

    while(fast != null && fast.next != null) {
        slow = slow.next;          // 1 hop
        fast = fast.next.next;     // 2 Hops 

        if(slow == fast)  // fast caught up to slow, so there is a loop
            return true;
    }
    return false;  // fast reached null, so the list terminates
}
106
Dave L.

Eine alternative Lösung zu Turtle and Rabbit, nicht ganz so schön, da ich die Liste vorübergehend ändere:

Die Idee ist, durch die Liste zu gehen und sie umzukehren, während Sie gehen. Wenn Sie dann zum ersten Mal einen Knoten erreichen, der bereits besucht wurde, zeigt sein nächster Zeiger nach hinten, wodurch die Iteration erneut in Richtung first weitergeht und dort endet.

Node prev = null;
Node cur = first;
while (cur != null) {
    Node next = cur.next;
    cur.next = prev;
    prev = cur;
    cur = next;
}
boolean hasCycle = prev == first && first != null && first.next != null;

// reconstruct the list
cur = prev;
prev = null;
while (cur != null) {
    Node next = cur.next;
    cur.next = prev;
    prev = cur;
    cur = next;
}

return hasCycle;

Testcode:

static void assertSameOrder(Node[] nodes) {
    for (int i = 0; i < nodes.length - 1; i++) {
        assert nodes[i].next == nodes[i + 1];
    }
}

public static void main(String[] args) {
    Node[] nodes = new Node[100];
    for (int i = 0; i < nodes.length; i++) {
        nodes[i] = new Node();
    }
    for (int i = 0; i < nodes.length - 1; i++) {
        nodes[i].next = nodes[i + 1];
    }
    Node first = nodes[0];
    Node max = nodes[nodes.length - 1];

    max.next = null;
    assert !hasCycle(first);
    assertSameOrder(nodes);
    max.next = first;
    assert hasCycle(first);
    assertSameOrder(nodes);
    max.next = max;
    assert hasCycle(first);
    assertSameOrder(nodes);
    max.next = nodes[50];
    assert hasCycle(first);
    assertSameOrder(nodes);
}
49
meriton

Besser als der Floyd-Algorithmus

Richard Brent beschrieb einen alternativen Zykluserkennungsalgorithmus , der dem Hasen und der Schildkröte (Floyd's cycle) ziemlich ähnlich ist, abgesehen davon, dass sich der langsame Knoten hier nicht bewegt, sondern später zur Position des "teleportiert" wird schneller Knoten in festen Intervallen. 

Die Beschreibung ist hier verfügbar: http://www.siafoo.net/algorithm/11 Brent behauptet, sein Algorithmus sei 24 bis 36% schneller als der Floyd-Zyklus-Algorithmus. O (n) zeitliche Komplexität, O(1) Raumkomplexität.

public static boolean hasLoop(Node root){
    if(root == null) return false;

    Node slow = root, fast = root;
    int taken = 0, limit = 2;

    while (fast.next != null) {
        fast = fast.next;
        taken++;
        if(slow == fast) return true;

        if(taken == limit){
            taken = 0;
            limit <<= 1;    // equivalent to limit *= 2;
            slow = fast;    // teleporting the turtle (to the hare's position) 
        }
    }
    return false;
}
46

Schildkröte und Hase

Werfen Sie einen Blick auf Pollards Rho-Algorithmus . Es ist nicht ganz dasselbe Problem, aber vielleicht verstehen Sie die Logik daraus und wenden sie für verknüpfte Listen an.

(Wenn Sie faul sind, können Sie einfach herausfinden - Zykluserkennung - den Teil über die Schildkröte und den Hasen überprüfen.)

Dies erfordert nur eine lineare Zeit und zwei zusätzliche Zeiger.

In Java:

boolean hasLoop( Node first ) {
    if ( first == null ) return false;

    Node turtle = first;
    Node hare = first;

    while ( hare.next != null && hare.next.next != null ) {
         turtle = turtle.next;
         hare = hare.next.next;

         if ( turtle == hare ) return true;
    }

    return false;
}

(Die meisten Lösungen suchen nicht nach next und next.next nach Nullen. Da die Schildkröte immer hinterher ist, müssen Sie sie nicht auf Null überprüfen - der Hase hat dies bereits getan.)

28
Larry

Der Benutzer unicornaddict hat oben einen Nice-Algorithmus, aber leider enthält er einen Fehler für ungeradzahlige Listen ungerader Länge> = 3. Das Problem ist, dass fast kurz vor dem Ende der Liste "hängen bleiben" kann, slow holt sich ein, und eine Schleife wird (falsch) erkannt.

Hier ist der korrigierte Algorithmus.

static boolean hasLoop(Node first) {

    if(first == null) // list does not exist..so no loop either.
        return false;

    Node slow, fast; // create two references.

    slow = fast = first; // make both refer to the start of the list.

    while(true) {
        slow = slow.next;          // 1 hop.
        if(fast.next == null)
            fast = null;
        else
            fast = fast.next.next; // 2 Hops.

        if(fast == null) // if fast hits null..no loop.
            return false;

        if(slow == fast) // if the two ever meet...we must have a loop.
            return true;
    }
}
13
Carl Mäsak

Algorithmus

public static boolean hasCycle (LinkedList<Node> list)
{
    HashSet<Node> visited = new HashSet<Node>();

    for (Node n : list)
    {
        visited.add(n);

        if (visited.contains(n.next))
        {
            return true;
        }
    }

    return false;
}

Komplexität

Time ~ O(n)
Space ~ O(n)
9
Khaled.K

Die folgende Methode ist möglicherweise nicht die beste Methode - es ist O (n ^ 2). Es sollte jedoch dazu dienen, die Arbeit (eventuell) zu erledigen.

count_of_elements_so_far = 0;
for (each element in linked list)
{
    search for current element in first <count_of_elements_so_far>
    if found, then you have a loop
    else,count_of_elements_so_far++;
}
8
Sparky
public boolean hasLoop(Node start){   
   TreeSet<Node> set = new TreeSet<Node>();
   Node lookingAt = start;

   while (lookingAt.peek() != null){
       lookingAt = lookingAt.next;

       if (set.contains(lookingAt){
           return false;
        } else {
        set.put(lookingAt);
        }

        return true;
}   
// Inside our Node class:        
public Node peek(){
   return this.next;
}

Verzeihen Sie mir meine Unwissenheit (ich bin noch relativ neu in Java und Programmierung), aber warum sollte das nicht funktionieren? 

Ich denke, das löst nicht das ständige Raumproblem ... aber es kommt wenigstens in einer vernünftigen Zeit dazu, richtig? Es wird nur der Platz der verknüpften Liste sowie der Platz einer Menge mit n Elementen benötigt (wobei n die Anzahl der Elemente in der verknüpften Liste oder die Anzahl der Elemente ist, bis eine Schleife erreicht wird). Und für die schlimmste Fallanalyse denke ich, wäre O (nlog (n)). SortedSet-Lookups für contains () sind log (n) (überprüfen Sie den Javadoc, aber ich bin mir ziemlich sicher, dass TreeSets zugrunde liegende Struktur TreeMap ist, die wiederum ein rot-schwarzer Baum ist) und im schlimmsten Fall (keine Schleifen, oder Schleife ganz am Ende), muss es n Look-ups durchführen.

3
smessing

Wenn wir die Klasse Node einbetten dürfen, würde ich das Problem lösen, da ich es unten implementiert habe. hasLoop() läuft in der Zeit O(n) und nimmt nur den Platz von counter ein. Ist das eine geeignete Lösung? Oder gibt es eine Möglichkeit, dies zu tun, ohne Node einzubetten? (Natürlich gibt es in einer echten Implementierung mehr Methoden wie RemoveNode(Node n) usw.).

public class LinkedNodeList {
    Node first;
    Int count;

    LinkedNodeList(){
        first = null;
        count = 0;
    }

    LinkedNodeList(Node n){
        if (n.next != null){
            throw new error("must start with single node!");
        } else {
            first = n;
            count = 1;
        }
    }

    public void addNode(Node n){
        Node lookingAt = first;

        while(lookingAt.next != null){
            lookingAt = lookingAt.next;
        }

        lookingAt.next = n;
        count++;
    }

    public boolean hasLoop(){

        int counter = 0;
        Node lookingAt = first;

        while(lookingAt.next != null){
            counter++;
            if (count < counter){
                return false;
            } else {
               lookingAt = lookingAt.next;
            }
        }

        return true;

    }



    private class Node{
        Node next;
        ....
    }

}
3
smessing

In diesem Zusammenhang gibt es überall Textmaterialien. Ich wollte nur eine schematische Darstellung posten, die mir wirklich dabei half, das Konzept zu verstehen.

Wenn sich schnell und langsam am Punkt p treffen, 

Von fast gefahrene Strecke = a + b + c + b = a + 2b + c 

Zurückgelegte Strecke von slow = a + b

Da ist das Schnelle 2 mal schneller als das Langsame. Also a + 2b + c = 2 (a + b), dann erhalten wir a = c.

Wenn also ein weiterer langsamer Zeiger wieder von head nach q läuft, läuft der schnelle Zeiger von p nach q, so dass sie sich am Punkt q treffen.

 enter image description here

public ListNode detectCycle(ListNode head) {
    if(head == null || head.next==null)
        return null;

    ListNode slow = head;
    ListNode fast = head;

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

        /*
        if the 2 pointers meet, then the 
        dist from the meeting pt to start of loop 
        equals
        dist from head to start of loop
        */
        if (fast == slow){ //loop found
            slow = head;
            while(slow != fast){
                slow = slow.next;
                fast = fast.next;
            }
            return slow;
        }            
    }
    return null;
}
2
Neil
 // To detect whether a circular loop exists in a linked list
public boolean findCircularLoop() {
    Node slower, faster;
    slower = head;
    faster = head.next; // start faster one node ahead
    while (true) {

        // if the faster pointer encounters a NULL element
        if (faster == null || faster.next == null)
            return false;
        // if faster pointer ever equals slower or faster's next
        // pointer is ever equal to slower then it's a circular list
        else if (slower == faster || slower == faster.next)
            return true;
        else {
            // advance the pointers
            slower = slower.next;
            faster = faster.next.next;
        }
    }
}
1
Richa

Sie könnten es sogar in konstanter O(1) Zeit tun (obwohl es nicht sehr schnell oder effizient wäre): Es gibt eine begrenzte Anzahl von Knoten, die der Arbeitsspeicher Ihres Computers aufnehmen kann, beispielsweise N Datensätze. Wenn Sie mehr als N Datensätze durchlaufen, haben Sie eine Schleife.

1
Eduardo

Das Erkennen einer Schleife in einer verknüpften Liste kann auf eine der einfachsten Arten erfolgen, was zu einer Komplexität von O(N) führt.

Erstellen Sie beim Durchlaufen der Liste beginnend mit head eine sortierte Liste von Adressen. Wenn Sie eine neue Adresse einfügen, prüfen Sie, ob die Adresse bereits in der sortierten Liste vorhanden ist. Dies erfordert O(logN).

0
Abhinav
boolean hasCycle(Node head) {

    boolean dec = false;
    Node first = head;
    Node sec = head;
    while(first != null && sec != null)
    {
        first = first.next;
        sec = sec.next.next;
        if(first == sec )
        {
            dec = true;
            break;
        }

    }
        return dec;
}

Verwenden Sie die obige Funktion, um eine Schleife in einer verknüpften Liste in Java zu erkennen.

0
Aditya Parmar

Sie können auch den Floyd-Tortoise-Algorithmus verwenden, wie in den obigen Antworten vorgeschlagen.

Dieser Algorithmus kann überprüfen, ob eine einfach verknüpfte Liste einen geschlossenen Zyklus hat .. Dies kann durch Iteration einer Liste mit zwei Zeigern erreicht werden, die sich mit unterschiedlicher Geschwindigkeit bewegen. Auf diese Weise treffen sich die beiden Zeiger, wenn es einen Zyklus gibt, irgendwann in der Zukunft.

Bitte schauen Sie sich meinen blog post über die Datenstruktur der verknüpften Listen an, wo ich auch einen Code-Ausschnitt mit einer Implementierung des oben genannten Algorithmus in Java-Sprache eingefügt habe.

Grüße,

Andreas (@xnorcode)

0
xnorcode

Ich könnte furchtbar spät und neu sein, um diesen Thread zu behandeln. Aber dennoch..

Warum kann die Adresse des Knotens und der "nächste" Knoten nicht in einer Tabelle gespeichert werden?

Wenn wir diesen Weg tabellieren könnten

node present: (present node addr) (next node address)

node 1: addr1: 0x100 addr2: 0x200 ( no present node address till this point had 0x200)
node 2: addr2: 0x200 addr3: 0x300 ( no present node address till this point had 0x300)
node 3: addr3: 0x300 addr4: 0x400 ( no present node address till this point had 0x400)
node 4: addr4: 0x400 addr5: 0x500 ( no present node address till this point had 0x500)
node 5: addr5: 0x500 addr6: 0x600 ( no present node address till this point had 0x600)
node 6: addr6: 0x600 addr4: 0x400 ( ONE present node address till this point had 0x400)

Daher entsteht ein Zyklus.

0
Adit Ya

Hier ist die Lösung zur Erkennung des Zyklus.

public boolean hasCycle(ListNode head) {
            ListNode slow =head;
            ListNode fast =head;

            while(fast!=null && fast.next!=null){
                slow = slow.next; // slow pointer only one hop
                fast = fast.next.next; // fast pointer two Hops 

                if(slow == fast)    return true; // retrun true if fast meet slow pointer
            }

            return false; // return false if fast pointer stop at end 
        }
0
vishwaraj

Hier ist mein lauffähiger Code. 

Was ich getan habe, ist, die verknüpfte Liste unter Verwendung von drei temporären Knoten (Raumkomplexität O(1)), die die Verknüpfungen verfolgen, zu überprüfen. 

Die interessante Tatsache dabei ist, den Zyklus in der verketteten Liste zu erkennen, da Sie im weiteren Verlauf nicht davon ausgehen, zum Startpunkt (Wurzelknoten) zurückzukehren, und einer der temporären Knoten sollte auf Null gehen, wenn Sie nicht einen Zyklus haben, dh auf den Wurzelknoten zeigt. 

Die zeitliche Komplexität dieses Algorithmus ist O(n) und die Raumkomplexität ist O(1).

Hier ist der Klassenknoten für die verknüpfte Liste:

public class LinkedNode{
    public LinkedNode next;
}

Hier ist der Hauptcode mit einem einfachen Testfall von drei Knoten, von denen der letzte Knoten auf den zweiten Knoten zeigt:

    public static boolean checkLoopInLinkedList(LinkedNode root){

        if (root == null || root.next == null) return false;

        LinkedNode current1 = root, current2 = root.next, current3 = root.next.next;
        root.next = null;
        current2.next = current1;

        while(current3 != null){
            if(current3 == root) return true;

            current1 = current2;
            current2 = current3;
            current3 = current3.next;

            current2.next = current1;
        }
        return false;
    }

Hier ist ein einfacher Testfall von drei Knoten, von denen der letzte auf den zweiten Knoten zeigt:

public class questions{
    public static void main(String [] args){

        LinkedNode n1 = new LinkedNode();
        LinkedNode n2 = new LinkedNode();
        LinkedNode n3 = new LinkedNode();
        n1.next = n2;
        n2.next = n3;
        n3.next = n2;

        System.out.print(checkLoopInLinkedList(n1));
    }
}
0
Habib Karbasian

Hier ist meine Lösung in Java

boolean detectLoop(Node head){
    Node fastRunner = head;
    Node slowRunner = head;
    while(fastRunner != null && slowRunner !=null && fastRunner.next != null){
        fastRunner = fastRunner.next.next;
        slowRunner = slowRunner.next;
        if(fastRunner == slowRunner){
            return true;
        }
    }
    return false;
}
0
Irshad ck

Dieser Code ist optimiert und liefert ein schnelleres Ergebnis als mit demjenigen, der als beste Antwort ausgewählt wurde. Dieser Code erspart es, einen sehr langen Prozess der Verfolgung des Vorwärts- und Rückwärtsknotens auszuführen, der im folgenden Fall auftritt, wenn wir dem 'besten' folgen answer 'method.Look durch den Trockenlauf der folgenden und Sie werden erkennen, was ich versuche zu sagen. Dann schauen Sie sich das Problem durch die unten angegebene Methode an und messen Sie das Nein. Schritte unternommen, um die Antwort zu finden.

1-> 2-> 9-> 3 ^ -------- ^ 

Hier ist der Code:

boolean loop(node *head)
{
 node *back=head;
 node *front=head;

 while(front && front->next)
 {
  front=front->next->next;
  if(back==front)
  return true;
  else
  back=back->next;
 }
return false
}
0
Sarthak Mehra

Ich kann nicht erkennen, dass dies eine festgelegte Zeit oder Platz erfordert, beide werden mit der Größe der Liste größer.

Ich würde eine IdentityHashMap verwenden (da es noch kein IdentityHashSet gibt) und jeden Knoten in der Map speichern. Bevor ein Knoten gespeichert wird, rufen Sie den Schlüssel auf. Wenn der Knoten bereits existiert, haben Sie einen Zyklus.

ItentityHashMap verwendet == anstelle von .equals, sodass Sie prüfen, wo sich das Objekt im Speicher befindet, und nicht, ob es den gleichen Inhalt hat.

0
TofuBeer

// verknüpfte Listensuchschleifenfunktion

int findLoop(struct Node* head)
{
    struct Node* slow = head, *fast = head;
    while(slow && fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
            return 1;
    }
 return 0;
}
0
Sonu Mishra