Comment détecter une boucle dans une liste chaînée?

dites que vous avez une structure de liste liée en Java. Il est composé de noeuds:

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

et chaque noeud pointe vers le noeud suivant, à l'exception du dernier noeud, qui a nul pour next. Supposons qu'il y ait une possibilité que la liste puisse contenir une boucle - c'est-à-dire que le noeud final, au lieu d'avoir un nul, a une référence à l'un des noeuds de la liste qui est venu avant lui.

Quelle est la meilleure façon d'écrire

boolean hasLoop(Node first)

qui retournerait true si le noeud donné est le premier D'une liste avec une boucle, et false sinon? Comment pourriez-vous écrire afin qu'il prenne une quantité constante de l'espace et un temps raisonnable?

Voici une image de ce à quoi ressemble une liste avec une boucle:

alt text

386
demandé sur Nicolas Barbulesco 2010-04-18 21:08:53

23 réponses

vous pouvez utiliser algorithme de recherche de cycle de Floyd , également connu sous le nom algorithme de tortue et de lièvre .



L'idée est d'avoir deux références à la liste et de les déplacer à plusieurs vitesses . Déplacer l'un vers l'avant par 1 nœud et l'autre par 2 nœuds.

  • si la liste liée contient une boucle ils va certainement de répondre.
  • autre les deux références(ou leur next ) deviendra null .

Java fonction de la mise en œuvre de l'algorithme:

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;
    }
}
493
répondu codaddict 2017-07-24 05:28:52

voici un affinement de la solution Fast/Slow, qui gère correctement les listes de longueurs impaires et améliore la clarté.

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
}
102
répondu Dave L. 2010-07-21 16:55:34

une solution alternative à la tortue et au lapin, pas aussi sympa, car je change temporairement la liste:

L'idée est de parcourir la liste, et à l'inverse comme vous allez. Ensuite, lorsque vous atteignez un noeud qui a déjà été visité, son prochain pointeur pointera "en arrière", provoquant l'itération de procéder vers first à nouveau, où il se termine.

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;

code D'essai:

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);
}
47
répondu meriton 2010-04-20 01:25:00

meilleur que L'algorithme de Floyd

Richard Brent a décrit un algorithme alternatif de détection de cycle , qui est à peu près comme le lièvre et la tortue [cycle de Floyd] sauf que, le noeud lent ici ne bouge pas, mais est plus tard" téléporté " à la position du noeud rapide à intervalles fixes.

la description est disponible ici : http://www.siafoo.net/algorithm/11 Brent prétend que son algorithme est 24 à 36% plus rapide que L'algorithme de cycle de Floyd. O(n) complexité temporelle, O (1) complexité spatiale.

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;
}
45
répondu Ashok Bijoy Debnath 2015-11-19 10:55:10

tortue et lièvre

regardez algorithme Rho de Pollard . Ce n'est pas tout à fait le même problème, mais peut-être que vous comprendrez la logique de celui-ci, et l'appliquerez pour les listes liées.

(si vous êtes paresseux, vous pouvez simplement vérifier cycle detection -- vérifier la partie sur la tortue et le lièvre.)

cela ne nécessite que du temps linéaire, et 2 extra pointeur.

En 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;
}

(la plus grande partie de la solution ne vérifie pas les valeurs nulles de next et next.next . De plus, comme la tortue est toujours en retard, vous n'avez pas à vérifier si elle est nulle -- le lièvre l'a déjà fait.)

28
répondu Larry 2015-03-11 13:31:29

L'utilisateur unicornaddict a une belle algorithme ci-dessus, mais malheureusement, il contient un bug pour les non-loopy listes de longueur impaire >= 3. Le problème est que fast peut être "coincé", juste avant la fin de la liste, slow rattrape, et une boucle est (à tort) détecté.

Voici l'algorithme corrigé.

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
répondu Carl Mäsak 2017-05-23 12:34:45

peuvent ne pas être la meilleure méthode, c'est de O(n^2). Cependant, il doit servir à faire le travail (éventuellement).

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
répondu Sparky 2010-04-18 17:14:33

algorithme

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;
}

complexité

Time ~ O(n)
Space ~ O(n)
8
répondu Khaled.K 2015-06-17 19:53:41
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;
}

Pardonnez-moi mon ignorance (je suis encore assez nouveau en Java et en programmation), mais pourquoi cela ne marcherait-il pas?

je suppose que cela ne résout pas le problème de l'espace constant... mais elle permet au moins d'y arriver dans un délai raisonnable, correct? Il ne fera que prendre de l'espace de la liste, en plus de l'espace d'un ensemble à n éléments (où n est le nombre d'éléments dans la liste liée, ou le nombre d'éléments jusqu'à ce qu'il atteigne une boucle). Et pour le temps, dans le pire des cas l'analyse, je pense, suggérerait O(nlog (n)). Les recherches SortedSet pour contains () sont log (n) (vérifiez le javadoc, mais je suis à peu près sûr que la structure sous-jacente de TreeSet est TreeMap, qui à son tour est un arbre rouge-noir), et dans le pire des cas (pas de boucles, ou boucle à la toute fin), il devra faire n recherches.

3
répondu smessing 2010-04-21 06:53:49

si nous sommes autorisés à intégrer la classe Node , Je résoudrai le problème comme je l'ai mis en œuvre ci-dessous. hasLoop() court en O(n) temps, et ne prend que l'espace de counter . Cela vous semble comme une solution appropriée? Ou y a-t-il un moyen de le faire sans inclure Node ? (Évidemment, dans une mise en œuvre réelle il y aurait plus de méthodes, comme RemoveNode(Node n) , etc.)

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
répondu smessing 2010-04-21 14:46:21

vous pourriez même le faire en temps O(1) constant (bien que ce ne serait pas très rapide ou efficace): Il ya une quantité limitée de noeuds de la mémoire de votre ordinateur peut tenir, disons n dossiers. Si vous parcourir plus de N enregistrements, alors vous avez une boucle.

1
répondu Eduardo 2012-10-15 09:51:48
 // 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
répondu Richa 2016-04-29 11:13:52

Je ne vois aucune façon de faire cela prendre une quantité fixe de temps ou d'espace, les deux vont augmenter avec la taille de la liste.

j'utiliserais une IdentityHashMap (étant donné qu'il n'y a pas encore D'IdentityHashSet) et je stockerais chaque noeud dans la carte. Avant qu'un noeud soit stocké, vous appelleriez containsKey dessus. Si le noeud existe déjà, vous avez un cycle.

ItentityHashMap uses = = au lieu de .égal de sorte que vous vérifiez où le l'objet est en mémoire plutôt que si il a le même contenu.

0
répondu TofuBeer 2010-04-18 17:22:07

je pourrais être terriblement en retard et nouveau pour gérer ce fil. Mais tout de même..

pourquoi ne pas stocker dans une table l'adresse du noeud et le point suivant du noeud

Si nous pouvions nous dépouiller de cette façon,

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)

il y a donc un cycle formé.

0
répondu Adit Ya 2014-01-24 08:40:32

Voici mon code d'exécution.

ce que j'ai fait est de revenir sur la liste des liens en utilisant trois noeuds temporaires ("Space complexity O(1) ) qui gardent la trace des liens.

le fait intéressant de le faire est d'aider à détecter le cycle dans la liste liée parce que vous allez de l'avant, vous ne vous attendez pas à revenir au point de départ (noeud racine) et l'un des noeuds temporaires devrait aller à null sauf si vous avez un cycle ce qui signifie qu'il pointe vers le nœud racine.

la complexité temporelle de cet algorithme est O(n) et la complexité spatiale est O(1) .

voici le noeud de classe pour la liste liée:

public class LinkedNode{
    public LinkedNode next;
}

voici le code principal avec un cas de test simple de trois noeuds que le dernier noeud pointant vers le second noeud:

    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;
    }

voici un cas de test simple de trois noeuds que le dernier noeud pointant vers le second node:

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
répondu Habib Karbasian 2016-02-10 06:01:27

ce code est optimisé et produira des résultats plus rapidement qu'avec celui choisi comme la meilleure réponse.Ce code sauve d'aller dans un très long processus de poursuite du pointeur de noeud avant et arrière qui se produira dans le cas suivant si nous suivons la méthode de la "meilleure réponse".Regardez à travers la répétition de ce qui suit et vous vous rendrez compte de ce que j'essaie de dire.Ensuite, regardez le problème à travers la méthode donnée ci-dessous et mesurez le non. des mesures prises pour trouver la réponse.

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

voici le 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
répondu Sarthak Mehra 2016-06-27 20:03:45
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;
}

utilisez la fonction ci-dessus pour détecter une boucle dans linkedlist en java.

0
répondu Aditya Parmar 2017-09-05 21:09:15

la détection d'une boucle dans une liste liée peut être faite de l'une des façons les plus simples, qui résulte en O(N) complexité.

en parcourant la liste à partir de head, créez une liste d'adresses triée. Lorsque vous insérez une nouvelle adresse, vérifiez si l'adresse est déjà présente dans la liste triée, ce qui prend la complexité O(logN).

0
répondu Abhinav 2018-04-12 02:46:46

vous pouvez utiliser L'algorithme tortoise de Floyd comme suggéré dans les réponses ci-dessus aussi.

cet algorithme peut vérifier si une liste à un seul lien a un cycle fermé. Cela peut être réalisé en itérant une liste avec deux pointeurs qui se déplacent à une vitesse différente. De cette façon, s'il y a un cycle, les deux indicateurs se rencontreront à un moment donné dans le futur.

s'il vous Plaît n'hésitez pas à consulter mon blog post sur les listes de données structure, où j'ai également inclus un extrait de code avec une implémentation de l'algorithme mentionné ci-dessus en langage java.

Cordialement,

Andreas (@xnorcode)

0
répondu xnorcode 2018-05-03 22:09:13

Voici la solution pour détecter le cycle.

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
répondu vishwaraj 2018-06-30 17:46:56

Cette approche a l'espace de la surcharge, une simplification de la mise en œuvre:

La boucle

peut être identifiée en stockant les noeuds dans une carte. Et avant de mettre le noeud; vérifiez si le noeud existe déjà. Si le noeud existe déjà dans la carte, alors cela signifie que la liste liée a une boucle.

public boolean loopDetector(Node<E> first) {  
       Node<E> t = first;  
       Map<Node<E>, Node<E>> map = new IdentityHashMap<Node<E>, Node<E>>();  
       while (t != null) {  
            if (map.containsKey(t)) {  
                 System.out.println(" duplicate Node is --" + t  
                           + " having value :" + t.data);  

                 return true;  
            } else {  
                 map.put(t, t);  
            }  
            t = t.next;  
       }  
       return false;  
  }  
-1
répondu rai.skumar 2016-02-15 07:58:00

voici ma solution en 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;
}
-1
répondu Irshad ck 2017-09-24 12:50:23
public boolean isCircular() {

    if (head == null)
        return false;

    Node temp1 = head;
    Node temp2 = head;

    try {
        while (temp2.next != null) {

            temp2 = temp2.next.next.next;
            temp1 = temp1.next;

            if (temp1 == temp2 || temp1 == temp2.next) 
                return true;    

        }
    } catch (NullPointerException ex) {
        return false;

    }

    return false;

}
-2
répondu edst 2013-01-16 05:05:00