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


434

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 le suivant. Supposons qu'il existe une possibilité que la liste contienne une boucle - c'est-à-dire que le nœud final, au lieu d'avoir un null, 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 truesi le nœud donné est le premier d'une liste avec une boucle, et falsesinon? Comment pourriez-vous écrire pour que cela prenne un espace constant et un temps raisonnable?

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

texte alternatif


50
Wow..J'adorerais travailler pour cet employeur finite amount of space and a reasonable amount of time?:)
codaddict

10
@SLaks - la boucle n'a pas besoin de revenir au premier nœud. Il peut revenir en boucle à mi-chemin.
jjujuma

109
Les réponses ci-dessous méritent d'être lues, mais les questions d'entrevue comme celle-ci sont terribles. Soit vous connaissez la réponse (c'est-à-dire que vous avez vu une variante de l'algorithme de Floyd), soit vous ne le savez pas, et cela ne fait rien pour tester votre raisonnement ou votre capacité de conception.
GaryF

3
Pour être juste, la plupart des «algorithmes de connaissance» sont comme ça - à moins que vous ne fassiez des choses au niveau de la recherche!
Larry

12
@GaryF Et pourtant, il serait révélateur de savoir ce qu'ils feraient s'ils ne connaissaient pas la réponse. Par exemple, quelles mesures prendraient-ils, avec qui travailleraient-ils, que feraient-ils pour surmonter un manque de connaissances algorithmiques?
Chris Knight

Réponses:


538

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 . Avancez un par 1nœud et l'autre par 2nœuds.

  • Si la liste chaînée a une boucle, ils se rencontreront certainement .
  • Sinon, l'une des deux références (ou la leur 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;
    }
}

29
Vous devez également effectuer une vérification nulle fast.nextavant de nextif(fast.next!=null)fast=fast.next.next;
rappeler

12
vous devez vérifier non seulement (lent == rapide) mais: (lent == rapide || slow.next == rapide) pour éviter de sauter le rapide sur le lent
Oleg Razgulyaev

13
j'avais tort: ​​rapide ne peut pas sauter lentement, parce que sauter rapidement à l'étape suivante rapide devrait avoir la même position que lent :)
Oleg Razgulyaev

4
La vérification de slow == null est redondante à moins que la liste ne comporte qu'un seul nœud. Vous pouvez également supprimer un appel à Node.next. Voici une version plus simple et plus rapide de la boucle: pastie.org/927591
Kay Sarraute

22
Vous devriez vraiment citer vos références. Cet algorithme a été inventé par Robert Floyd dans les années 60, il est connu comme l'algorithme de recherche de cycle de Floyd, alias. L'algorithme de la tortue et du lièvre.
joshperry

127

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
}

2
Agréable et succinct. Ce code peut être optimisé en vérifiant si lent == rapide || (fast.next! = null && slow = fast.next); :)
arachnode.net

11
@ arachnode.net Ce n'est pas une optimisation. Si slow == fast.nextalors slowsera égal fastà la toute prochaine itération; il n'enregistre qu'une seule itération au maximum au détriment d'un test supplémentaire pour chaque itération.
Jason C

@ ana01 slowne peut pas devenir nul auparavant fastcar il suit le même chemin de références (sauf si vous avez une modification simultanée de la liste, auquel cas tous les paris sont désactivés).
Dave L.

Par curiosité, comment cela fonctionne-t-il pour les nombres impairs? Le lièvre ne peut-il toujours pas passer la tortue sur des listes chaînées de longueur impaire?
theGreenCabbage

1
@theGreenCabbage À chaque itération de la boucle, le lièvre prend une longueur d'avance sur la tortue. Donc, si le lièvre est en retard de 3 étapes, la prochaine itération prend deux sauts et la tortue prend un bond, et maintenant le lièvre est en retard de 2 étapes. Après la prochaine itération, le lièvre est en retard de 1 saut, puis il est rattrapé exactement. Si le lièvre a pris 3 sauts tandis que la tortue en a pris un, il pourrait passer, car il gagnerait de 2 à chaque fois, mais comme il ne gagne que de 1 à chaque itération, il ne peut pas passer.
Dave L.

52

Mieux 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 nœud lent ici ne bouge pas, mais est plus tard "téléporté" à la position du nœud rapide à fixe intervalles.

La description est disponible ici: http://www.siafoo.net/algorithm/11 Brent affirme que son algorithme est de 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;
}

Cette réponse est géniale!
valin077

1
J'ai vraiment aimé votre réponse, je l'ai incluse sur mon blog - k2code.blogspot.in/2010/04/… .
kinshuk4

Pourquoi faut-il vérifier slow.next != null? Pour autant que je puisse voir slowest toujours derrière ou égal à fast.
TWiStErRob

Je l'ai fait il y a longtemps, quand j'ai commencé à apprendre des algorithmes. Modification du code. Merci :)
Ashok Bijoy Debnath

50

Une solution alternative à la tortue et au lapin, pas tout à fait aussi agréable, 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 pour la première fois un nœud qui a déjà été visité, son prochain pointeur pointera "vers l'arrière", provoquant l'itération à continuer vers firstlà 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);
}

L'inverse fonctionne-t-il correctement lorsque la boucle pointe vers un nœud autre que le premier? Si la liste chaînée initiale est comme ceci 1-> 2-> 3-> 4-> 5-> 2 (avec un cycle de 5 à 2), alors la liste inversée ressemble à 1-> 2 <-3 <-4 <-5? Et si c'est l'inverse, la liste reconstruite finale sera foirée?
Zenil

1
@Zenil: C'est pourquoi j'ai écrit ce dernier testcase, où le dernier nœud pointe vers le milieu de la liste. Si la reconstruction ne fonctionnait pas, ce test échouerait. À propos de votre exemple: l'inversion de 1-> 2-> 3-> 5-> 2 serait 1-> 2-> 5-> 4-> 3-> 2, car la boucle ne s'arrête qu'une fois la fin de la liste a été atteint, pas lorsque la fin de la boucle (que nous ne pouvons pas facilement détecter) a été atteinte.
meriton

28

Tortue et lièvre

Jetez un œil à l'algorithme rho de Pollard . Ce n'est pas tout à fait le même problème, mais peut-être que vous en comprendrez 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érifiez 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 plupart de la solution ne vérifie pas les deux nextet les next.nextvaleurs nulles. De plus, comme la tortue est toujours derrière, vous n'avez pas à la vérifier pour null - le lièvre l'a déjà fait.)


13

L'utilisateur unicornaddict a un bon algorithme ci-dessus, mais malheureusement il contient un bogue pour les listes non bouclées de longueur impaire> = 3. Le problème est que fastpeut se "bloquer" juste avant la fin de la liste, le slowrattraper, 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;
    }
}

10

Dans ce contexte, les matériaux textuels sont partout chargés. Je voulais juste publier une représentation schématique qui m'a vraiment aidé à saisir le concept.

Quand rapide et lent se rencontrent au point p,

Distance parcourue par rapide = a + b + c + b = a + 2b + c

Distance parcourue lentement = a + b

Puisque le jeûne est 2 fois plus rapide que le lent. Donc a + 2b + c = 2 (a + b) , alors nous obtenons a = c .

Ainsi, lorsqu'un autre pointeur lent s'exécute à nouveau de la tête à q , en même temps, le pointeur rapide s'exécute de p à q , de sorte qu'ils se rencontrent au point q ensemble.

entrez la description de l'image ici

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
Une image vaut plus que des milliers de mots. Merci pour l'explication claire et simple!
Calios

1
Meilleure explication sur Internet. Ajoutons simplement que cela prouve que le pointeur rapide et lent converge après un temps linéaire
VarunPandey

si aest plus grand que la longueur de boucle, fast fera plusieurs boucles et la formule distance (fast) = a + b + b + cchangera en a + (b+c) * k + bintroduisant un paramètre supplémentaire kqui compte le nombre de lopps faites par fast.
Ben

9

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)

Comment est la complexité de l'espace O (2n)?
Programmer345

@ user3543449 vous avez raison, cela devrait être juste n, corrigé
Khaled.K

1
Il s'agit en fait du temps ~ O (n ^ 2) car chacun contient une vérification pour qu'une ArrayList prenne O (n) et il y en a O (n). Utilisez plutôt un HashSet pour un temps linéaire.
Dave L.

3
Cela ne teste pas les cycles mais les valeurs en double à l'aide des éléments equalset hashCode. Ce n'est pas la même chose. Et cela déréférence nullsur le dernier élément. Et la question n'a rien dit sur le stockage des nœuds dans unLinkedList .
Lii

2
@Lii c'est un pseudo-code, ce n'est pas un code Java, c'est pourquoi je l'ai intitulé avec Algorithm
Khaled.K

8

Ce qui suit n'est peut-être pas 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++;
}

Comment sauriez-vous combien d'éléments sont dans la liste pour faire le for ()?
Jethro Larson

@JethroLarson: le dernier nœud d'une liste chaînée pointe vers une adresse connue (dans de nombreuses implémentations, il s'agit de NULL). Mettez fin à la boucle for lorsque cette adresse connue est atteinte.
Sparky

3
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 cela ne fonctionnerait-il pas?

Je suppose que cela ne résout pas le problème d'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, l'analyse du pire des cas, je pense, suggérerait O (nlog (n)). Les recherches de 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 à 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.


2
Oui, une solution avec une sorte de Set fonctionne bien, mais nécessite un espace proportionnel à la taille de la liste.
jjujuma

3

Si nous sommes autorisés à intégrer la classe Node, je résoudrais le problème tel que je l'ai implémenté ci-dessous.hasLoop()s'exécute en temps O (n) et ne prend que l'espace de counter. Cela semble-t-il une solution appropriée? Ou existe-t-il un moyen de le faire sans intégration Node? (Évidemment, 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;
        ....
    }

}

1

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


Ce n'est pas O (1), cet algorithme n'a pas de complexité temporelle significative en notation big-O. La notation big-O ne vous indique que les performances dans la limite lorsque la taille d'entrée va à l'infini. Donc , si votre algorithme se base sur l'hypothèse qu'il n'y existe pas de liste avec plus de N éléments pour une grande N, la limite du temps d' exécution que la taille de la liste est l' infini approche non défini. Par conséquent, la complexité n'est pas "O (n'importe quoi)".
fgp

1
 // 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
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 une liste liée en Java.


2
Presque la même que ma réponse ci-dessus, mais a un problème. Il lèvera une NullPointerException pour les listes avec des listes de longueur impaire (sans boucles). Par exemple, si head.next est null, alors sec.next.next lancera un NPE.
Dave L.

1

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) à l'aide de hashmap ou O (NlogN) à l'aide d'une approche basée sur le tri.

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


La complexité de cette approche est O (N log N)
fgp

0

Je ne vois aucun moyen de faire en sorte que cela prenne un temps ou un espace fixe, les deux augmenteront avec la taille de la liste.

Je voudrais utiliser un IdentityHashMap (étant donné qu'il n'y a pas encore d'IdentityHashSet) et stocker chaque nœud dans la carte. Avant qu'un nœud ne soit stocké, vous devez appeler containsKey dessus. Si le nœud existe déjà, vous avez un cycle.

ItentityHashMap utilise == au lieu de .equals afin que vous vérifiiez où l'objet est en mémoire plutôt que s'il a le même contenu.


3
Il est certainement impossible que cela prenne un temps fixe, car il pourrait y avoir une boucle à la toute fin de la liste, donc la liste entière doit être visitée. Cependant, l'algorithme Fast / Slow illustre une solution utilisant une quantité fixe de mémoire.
Dave L.

Cela ne fait-il pas référence à son comportement asymptotique, c'est-à-dire qu'il est linéaire O (n) où n est la longueur de la liste. Fixé serait O (1)
Mark Robson

0

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

Pourquoi ne peut-on pas stocker l'adresse du nœud et le nœud "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)

Il y a donc un cycle formé.


Votre solution ne répond pas à l'exigence de "quantité constante d'espace".
Arnaud

0

Voici mon code exécutable.

Ce que j'ai fait est de révérer la liste des liens en utilisant trois nœuds temporaires (complexité de l'espace O(1)) qui gardent une 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 à revenir au point de départ (nœud racine) et l'un des nœuds temporaires devrait aller à null, sauf si vous avoir un cycle qui signifie qu'il pointe vers le nœud racine.

La complexité temporelle de cet algorithme est O(n)la complexité spatiale 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 pointe 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 pointe 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));
    }
}

0

Ce code est optimisé et produira un résultat plus rapidement qu'avec celui choisi comme meilleure réponse.Ce code évite d'entrer 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 le `` meilleur answer 'method.Look à travers la marche à sec de ce qui suit et vous réaliserez ce que j'essaie de dire.Ensuite, regardez le problème à travers la méthode 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
}

Êtes-vous sûr que cela donne le bon résultat dans toutes les situations? Si vous exécutez cet algorithme sur la liste 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 3 -> ..., je pense qu'il retournerait 4 comme tête, alors que vous vouliez 3.
Sunreef

La question est simplement de savoir s'il existe une boucle ou non.Dans ce cas, oui, la question fonctionnera parfaitement et obtiendra le résultat booléen souhaité pour le cas.Si vous voulez le nœud exact d'où la boucle a commencé, alors nous besoin d'ajouter quelque chose de plus au code.Mais en ce qui concerne la production d'un résultat, cela produira une conclusion plus rapide.
Sarthak Mehra

Vous n'avez pas lu correctement la question: quelle est la meilleure façon d'écrire boolean hasLoop(Node first)qui retournerait vrai si le nœud donné est le premier d'une liste avec une boucle, et faux sinon?
Sunreef

Voici la séquence d'essai pour votre liste: la première valeur signifie le pointeur arrière et la deuxième partie signifie le pointeur avant. (1,1) - (1,3) - (2,3) - (2,5) - (3,5) - (3,7) - (4,7) - (4,4).
Sarthak Mehra

En fait, je me rends compte maintenant qu'il y a deux façons de comprendre la question (ou du moins je vois deux interprétations différentes). Votre algorithme est correct si vous recherchez simplement une boucle. Mais je pensais que la question demandait où la boucle commençait.
Sunreef

0

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

0

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 liée individuellement a un cycle fermé. Ceci peut être réalisé en itérant une liste avec deux pointeurs qui se déplaceront à une vitesse différente. De cette façon, s'il y a un cycle, les deux pointeurs se rencontreront à un moment donné dans le futur.

N'hésitez pas à consulter mon blog 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 susmentionné en langage java.

Cordialement,

Andreas (@xnorcode)


0

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

// fonction de boucle de recherche de liste liée

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

-1

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

La boucle peut être identifiée en stockant les nœuds dans une carte. Et avant de mettre le nœud; vérifiez si le nœud 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;  
  }  

Cela ne répond pas à la quantité constante de restriction d' espace donnée dans la question!
dedek

convenez qu'il y a de l'espace au-dessus; c'est une autre approche pour résoudre ce problème. L'approche évidente est l'algorithme de tortue et de harse.
rai.skumar

@downvoter, il serait également utile d'expliquer la raison.
rai.skumar

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

}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.