java chainées - Comment détecter une boucle dans une liste chaînée?





d'une créer (20)


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 relativement nouveau pour Java et la programmation), mais pourquoi ce qui précède ne fonctionnerait-il pas?

Je suppose que cela ne résout pas la question de l'espace constant ... mais il y arrive au moins dans un délai raisonnable, n'est-ce pas? Il ne prendra que l'espace de la liste chaînée plus l'espace d'un ensemble avec n éléments (où n est le nombre d'éléments dans la liste chaînée, ou le nombre d'éléments jusqu'à ce qu'il atteigne une boucle). Et pour le temps, je crois que l'analyse de la pire hypothèse suggère O (nlog (n)). Les recherches SortedSet pour contains () sont log (n) (vérifiez le javadoc, mais je suis sûr que la structure sous-jacente de TreeSet est TreeMap, qui est à son tour un arbre rouge-noir), et dans le pire des cas (pas de boucles, ou boucle à la toute fin), il faudra faire n des recherches.

Supposons que vous ayez une structure de liste liée en Java. Il est composé de nœuds:

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

et chaque nœud pointe vers le nœud suivant, à l'exception du dernier nœud, qui a la valeur null pour la suivante. Supposons qu'il existe une possibilité que la liste puisse contenir une boucle - c'est-à-dire que le nœud final, au lieu d'avoir une valeur nulle, a une référence à l'un des nœuds de la liste qui l'a précédé.

Quelle est la meilleure façon d'écrire

boolean hasLoop(Node first)

qui retournerait true si le Node donné est le premier d'une liste avec une boucle, et false sinon? Comment pourriez-vous écrire pour que cela prenne une quantité constante d'espace et un temps raisonnable?

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




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;

}



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 dans Java.




Je pourrais être terriblement en retard et nouveau pour gérer ce fil. Mais reste..

Pourquoi ne peut pas stocker l'adresse du noeud et le noeud "suivant" pointé dans une table

Si nous pouvions tabuler 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)

D'où il y a un cycle formé.




Voici mon code exécutable.

Ce que j'ai fait est de vénérer la liste liée en utilisant trois noeuds temporaires (complexité de l'espace 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 chaînée car à mesure que vous avancez, vous ne vous attendez pas à retourner au point de départ (nœud racine) et l'un des nœuds temporaires devrait passer à null sauf si vous avoir un cycle qui signifie qu'il pointe vers le noeud racine.

La complexité temporelle de cet algorithme est O(n) et la complexité de l'espace est O(1) .

Voici le nœud de classe pour la liste chaînée:

public class LinkedNode{
    public LinkedNode next;
}

Voici le code principal avec un cas de test simple de trois nœuds que le dernier nœud pointant vers le deuxième nœud:

    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 nœuds que le dernier nœud pointant vers le deuxième nœud:

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



Cette approche a une surcharge de l'espace, mais une implémentation plus simple:

La boucle peut être identifiée en stockant des nœuds dans une carte. Et avant de mettre le noeud; Vérifiez si le noeud existe déjà. Si le nœud existe déjà dans la carte, 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;  
  }  



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)



Vous pouvez même le faire en O (1) constant (bien que ce ne soit pas très rapide ni efficace): Il y a un nombre limité de nœuds que la mémoire de votre ordinateur peut contenir, disons N enregistrements. Si vous traversez plus de N enregistrements, alors vous avez une boucle.




Si nous sommes autorisés à intégrer le Node classe, je résoudrais le problème comme je l'ai implémenté ci-dessous. hasLoop() s'exécute en O (n) time, et ne prend que l'espace du counter . Est-ce que cela semble être une solution appropriée? Ou y a-t-il un moyen de le faire sans intégrer Node ? (Evidemment, dans une implémentation 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;
        ....
    }

}



Voici un raffinement 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
}



Ce code est optimisé et produira un résultat plus rapide qu'avec celui choisi comme la meilleure réponse. Ce code évite d'aller dans un très long processus de poursuite du pointeur de nœud avant et arrière qui se produira dans le cas suivant si nous suivons les méthode de réponse.Look à travers la course à sec de ce qui suit et vous réaliserez ce que je suis en train de dire.Puis regardez le problème à travers la méthode donnée ci-dessous et de mesurer 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
}



La détection d'une boucle dans une liste chaînée peut être effectuée de l'une des manières les plus simples, ce qui entraîne une complexité O (N).

Lorsque vous parcourez la liste en commençant par la tête, créez une liste d'adresses triées. 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 en compte la complexité de O (logN).




Ce qui suit peut ne pas être la meilleure méthode - c'est O (n ^ 2). Cependant, cela devrait 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++;
}



Mieux que l'algorithme de Floyd

Richard Brent a décrit un algorithme de détection de cycle alternatif , qui ressemble beaucoup au lièvre et à la tortue [cycle de Floyd], sauf que le nœud lent ne bouge pas, mais est ensuite "téléporté" à la position du nœud rapide à intervalles.

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

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



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

L'idée est de parcourir la liste et de l'inverser au fur et à mesure. Ensuite, lorsque vous atteignez un noeud qui a déjà été visité, son pointeur suivant pointe vers l'arrière, provoquant la répétition de l'itération vers la first où elle 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 de test:

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



Vous pouvez également utiliser l'algorithme de tortue de Floyd comme suggéré dans les réponses ci-dessus.

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éplaceront à des vitesses différentes. De cette manière, s'il y a un cycle, les deux pointeurs se rencontreront à un moment donné dans le futur.

N'hésitez pas à consulter mon article sur la structure de données des listes liées, 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)




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

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



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

L'idée est d'avoir deux références à la liste et de les déplacer à des vitesses différentes . Déplacez un en avant d' 1 noeud et l'autre de 2 noeuds.

  • Si la liste liée a une boucle, ils vont certainement se rencontrer.
  • Sinon l'une des deux références (ou la next ) deviendra null .

Fonction Java implémentant 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;
    }
}



Tortue et lièvre

Jetez un oeil à l'algorithme rho de Pollard . Ce n'est pas tout à fait le même problème, mais peut-être en comprendrez-vous la logique et l'appliquerez aux listes chaînées.

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

Cela ne nécessite que du temps linéaire et 2 pointeurs supplémentaires.

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 majeure partie de la solution ne vérifie pas à la fois le next et le next.next pour les next.next outre, puisque la tortue est toujours derrière, vous n'avez pas besoin de la vérifier pour la valeur null - le lièvre l'a déjà fait.)




Une syntaxe de boucle foreach est:

for (type obj:array) {...}

Exemple:

String[] s = {"Java", "Coffe", "Is", "Cool"};
for (String str:s /*s is the array*/) {
    System.out.println(str);
}

Sortie:

Java
Coffe
Is
Cool

AVERTISSEMENT: vous pouvez accéder aux éléments du tableau avec la boucle foreach, mais vous ne pouvez PAS les initialiser. Utilisez l'original for boucle pour cela.

AVERTISSEMENT: vous devez faire correspondre le type du tableau avec l'autre objet.

for (double b:s) // Invalid-double is not String

Si vous voulez éditer des éléments, utilisez l'original for boucle comme ceci:

for (int i = 0; i < s.length-1 /*-1 because of the 0 index */; i++) {
    if (i==1) //1 because once again I say the 0 index
        s[i]="2 is cool";
    else
        s[i] = "hello";
}

Maintenant, si nous jetons des s dans la console, nous obtenons:

hello
2 is cool
hello
hello






java algorithm data-structures linked-list