Come rilevare un loop in un elenco collegato?


434

Supponi di avere una struttura di elenchi collegati in Java. È composto da nodi:

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

e ciascun nodo punta al nodo successivo, tranne l'ultimo nodo, che ha null per il prossimo. Supponiamo che esista la possibilità che l'elenco possa contenere un ciclo, ovvero che il nodo finale, invece di avere un valore nullo, abbia un riferimento a uno dei nodi nell'elenco precedente.

Qual è il modo migliore di scrivere

boolean hasLoop(Node first)

che restituirebbe truese il nodo specificato fosse il primo di un elenco con un ciclo efalse altrimenti? Come hai potuto scrivere in modo che occupasse una quantità costante di spazio e una ragionevole quantità di tempo?

Ecco un'immagine di come appare un elenco con un ciclo:

testo alternativo


50
Wow ... mi piacerebbe lavorare per questo datore di lavoro finite amount of space and a reasonable amount of time?:)
codaddict

10
@SLaks: il loop non è necessario per tornare al primo nodo. Può tornare indietro a metà.
jjujuma,

109
Vale la pena leggere le risposte di seguito, ma le domande di intervista come questa sono terribili. O conosci la risposta (ovvero hai visto una variante dell'algoritmo di Floyd) oppure no, e non fa nulla per testare il tuo ragionamento o la tua abilità di progettazione.
GaryF,

3
Ad essere onesti, la maggior parte degli "algoritmi conoscitivi" è così - a meno che tu non stia facendo cose a livello di ricerca!
Larry,

12
@GaryF Eppure sarebbe rivelatore sapere cosa farebbero se non conoscessero la risposta. Ad esempio, quali passi prenderebbero, con chi lavorerebbero, cosa farebbero per superare la mancanza di conoscenze algoritmiche?
Chris Knight,

Risposte:


538

Puoi utilizzare l'algoritmo di ricerca del ciclo di Floyd , noto anche come algoritmo di tartaruga e lepre .

L'idea è di avere due riferimenti all'elenco e spostarli a velocità diverse . Sposta uno in avanti per 1nodo e l'altro per 2nodi.

  • Se l'elenco collegato ha un ciclo, si incontreranno sicuramente .
  • Altrimenti nextdiventerà uno dei due riferimenti (o il loro ) null.

Funzione Java che implementa l'algoritmo:

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
È inoltre necessario effettuare un controllo null fast.nextprima di chiamare nextnuovamente:if(fast.next!=null)fast=fast.next.next;
cmptrgeekken,

12
dovresti controllare non solo (lento == veloce) ma: (lento == veloce || slow.next == veloce) per evitare di saltare il veloce sopra il lento
Oleg Razgulyaev,

13
mi sbagliavo: il digiuno non può saltare lentamente, perché saltare sul lento sul prossimo passo veloce dovrebbe avere la stessa posizione di lento :)
Oleg Razgulyaev,

4
Il controllo per slow == null è ridondante a meno che l'elenco non abbia un solo nodo. Puoi anche eliminare una chiamata a Node.next. Ecco una versione più semplice e veloce del ciclo: pastie.org/927591
Kay Sarraute,

22
Dovresti davvero citare i tuoi riferimenti. Questo algoritmo è stato inventato da Robert Floyd negli anni '60, è noto come l'algoritmo di ricerca del ciclo di Floyd, aka. L'algoritmo di tartaruga e lepre.
joshperry,

127

Ecco un perfezionamento della soluzione Fast / Slow, che gestisce correttamente elenchi di lunghezze dispari e migliora la chiarezza.

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
Bello e succinto. Questo codice può essere ottimizzato controllando se lento == veloce || (fast.next! = null && slow = fast.next); :)
arachnode.net,

11
@ arachnode.net Non è un'ottimizzazione. Se slow == fast.nextallora slowsarà uguale fastall'iterazione successiva; salva al massimo una sola iterazione a spese di un test aggiuntivo per ogni iterazione.
Jason C

@ ana01 slownon può diventare nullo prima fastpoiché sta seguendo lo stesso percorso di riferimenti (a meno che tu non abbia una modifica simultanea dell'elenco nel qual caso tutte le scommesse sono disattivate).
Dave L.,

Per curiosità come funziona con i numeri dispari? La lepre non può ancora passare la tartaruga su elenchi collegati di lunghezza dispari?
theGreenCabbage,

1
@theGreenCabbage Ogni iterazione del ciclo la lepre fa 1 passo più avanti della tartaruga. Quindi se la lepre è dietro di 3 passi, poi la successiva iterazione ci vogliono due salti e la tartaruga fa un salto, e ora la lepre è dietro di 2 passi. Dopo la successiva iterazione, la lepre è dietro di un luppolo e poi viene raggiunta esattamente. Se la lepre faceva 3 salti mentre la tartaruga ne prendeva una, allora poteva saltare perché guadagnava 2 ogni volta, ma poiché guadagna solo 1 per ogni iterazione non può saltare oltre.
Dave L.,

52

Meglio dell'algoritmo di Floyd

Richard Brent ha descritto un algoritmo di rilevamento del ciclo alternativo , che è molto simile alla lepre e alla tartaruga [ciclo di Floyd], tranne per il fatto che il nodo lento qui non si muove, ma in seguito viene "teletrasportato" nella posizione del nodo veloce su fisso intervalli.

La descrizione è disponibile qui: http://www.siafoo.net/algorithm/11 Brent afferma che il suo algoritmo è dal 24 al 36% più veloce dell'algoritmo del ciclo dei Floyd. O (n) complessità temporale, O (1) complessità spaziale.

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

Questa risposta è fantastica!
valin077,

1
Mi è davvero piaciuta la tua risposta, inclusa nel mio blog - k2code.blogspot.in/2010/04/… .
Kinshuk4,

Perché è necessario controllare slow.next != null? Per quanto posso vedere slowè sempre dietro o uguale a fast.
TWiStErRob,

L'ho fatto tanto tempo fa, quando ho iniziato a studiare algoritmi. Modificato il codice. Grazie :)
Ashok Bijoy Debnath,

50

Una soluzione alternativa a Tartaruga e Coniglio, non altrettanto carina, poiché cambio temporaneamente l'elenco:

L'idea è quella di percorrere l'elenco e invertirlo mentre procedi. Quindi, quando si raggiunge per la prima volta un nodo che è già stato visitato, il puntatore successivo punterà "all'indietro", facendo sì che l'iterazione proceda di firstnuovo verso , dove termina.

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;

Codice di prova:

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

Il contrario funziona correttamente quando il loop punta a un nodo diverso dal primo? Se l'elenco dei collegamenti iniziale è come questo 1-> 2-> 3-> 4-> 5-> 2 (con un ciclo da 5 a 2), l'elenco invertito appare come 1-> 2 <-3 <-4 <-5? E se è il contrario, l'elenco ricostruito finale verrà rovinato?
Zenil

1
@Zenil: ecco perché ho scritto l'ultimo testcase, in cui l'ultimo nodo punta al centro dell'elenco. Se la ricostruzione non funzionasse, quel test fallirebbe. A proposito del tuo esempio: l'inversione di 1-> 2-> 3-> 5-> 2 sarebbe 1-> 2-> 5-> 4-> 3-> 2, perché il ciclo si interrompe solo una volta alla fine dell'elenco è stato raggiunto, non quando è stata raggiunta la fine del loop (che non possiamo facilmente rilevare).
Meriton

28

Tartaruga e lepre

Dai un'occhiata all'algoritmo rho di Pollard . Non è esattamente lo stesso problema, ma forse capirai la logica da esso e la applicherai per gli elenchi collegati.

(se sei pigro, puoi semplicemente controllare il rilevamento del ciclo - controlla la parte sulla tartaruga e sulla lepre.)

Ciò richiede solo tempo lineare e 2 puntatori extra.

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

(La maggior parte della soluzione non controlla né entrambi nextné i next.nextnull. Inoltre, poiché la tartaruga è sempre dietro, non è necessario controllarla per null - la lepre lo ha già fatto.)


13

L'utente unicornaddict ha un buon algoritmo sopra, ma sfortunatamente contiene un bug per elenchi non loopy di lunghezza dispari> = 3. Il problema è che fastpuò rimanere "bloccato" appena prima della fine dell'elenco,slow recuperarlo e viene rilevato (erroneamente) un loop.

Ecco l'algoritmo corretto.

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

In questo contesto, ci sono molti materiali testuali ovunque. Volevo solo pubblicare una rappresentazione schematica che mi ha davvero aiutato a capire il concetto.

Quando veloce e lento si incontrano al punto p,

Distanza percorsa da veloce = a + b + c + b = a + 2b + c

Distanza percorsa da lento = a + b

Poiché il veloce è 2 volte più veloce del lento. Quindi a + 2b + c = 2 (a + b) , quindi otteniamo a = c .

Pertanto, quando un altro puntatore lento viene eseguito nuovamente da head a q , allo stesso tempo, il puntatore veloce verrà eseguito da p a q , quindi si incontrano nel punto q insieme.

inserisci qui la descrizione dell'immagine

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
Un'immagine vale più di migliaia di parole. Grazie per la spiegazione chiara e semplice!
Calios,

1
Migliore spiegazione su internet. Aggiungo semplicemente che questo dimostra che il puntatore veloce e lento convergono dopo un tempo lineare
VarunPandey,

se aè maggiore della lunghezza del loop, allora veloce eseguirà loop multipli e la formula distance (fast) = a + b + b + ccambierà a + (b+c) * k + bintroducendo un parametro aggiuntivo kche conta il numero di lopp realizzati da uno veloce.
Ben

9

Algoritmo

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

Complessità

Time ~ O(n)
Space ~ O(n)

Come è la complessità spaziale O (2n)?
Programmatore345

@ user3543449 hai ragione, dovrebbe essere giusto n, risolto
Khaled.K

1
Questo è in realtà il tempo ~ O (n ^ 2) poiché ognuno contiene un controllo per un ArrayList prende O (n) e ce ne sono O (n). Utilizzare un HashSet invece per un tempo lineare.
Dave L.,

3
Questo non verifica i cicli ma i valori duplicati usando gli elementi equalse hashCode. Non è la stessa cosa E dereferenze nullsull'ultimo elemento. E la domanda non diceva nulla sulla memorizzazione dei nodi in a LinkedList.
Lii,

2
@Lii è uno pseudo-codice, non è un codice Java, ecco perché l'ho intitolato con Algorithm
Khaled.K

8

Il seguente potrebbe non essere il metodo migliore: è O (n ^ 2). Tuttavia, dovrebbe servire a portare a termine il lavoro (eventualmente).

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

Come faresti a sapere quanti elementi sono presenti nell'elenco per eseguire for ()?
Jethro Larson,

@JethroLarson: l'ultimo nodo in un elenco collegato punta a un indirizzo noto (in molte implementazioni, questo è NULL). Termina il ciclo for quando viene raggiunto l'indirizzo noto.
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;
}

Perdona la mia ignoranza (sono ancora abbastanza nuovo su Java e sulla programmazione), ma perché non dovrebbe funzionare?

Immagino che questo non risolva il costante problema di spazio ... ma almeno ci arriva in un tempo ragionevole, giusto? Occuperà solo lo spazio dell'elenco collegato più lo spazio di un set con n elementi (dove n è il numero di elementi nell'elenco collegato o il numero di elementi fino a raggiungere un ciclo). E per tempo, l'analisi del caso peggiore, penso, suggerirebbe O (nlog (n)). Le ricerche di SortedSet per contenga () sono log (n) (controlla javadoc, ma sono abbastanza sicuro che la struttura sottostante di TreeSet è TreeMap, il cui a sua volta è un albero rosso-nero), e nel peggiore dei casi (nessun loop, o loop alla fine), dovrà fare n ricerche.


2
Sì, una soluzione con un qualche tipo di Set funziona bene, ma richiede uno spazio proporzionale alla dimensione dell'elenco.
jjujuma,

3

Se ci fosse permesso di incorporare la classe Node, risolverei il problema come ho implementato di seguito. hasLoop()viene eseguito nel tempo O (n) e occupa solo lo spazio di counter. Sembra una soluzione appropriata? O c'è un modo per farlo senza incorporarlo Node? (Ovviamente, in una vera implementazione ci sarebbero più metodi, come RemoveNode(Node n), ecc.)

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

Potresti anche farlo in un tempo O (1) costante (anche se non sarebbe molto veloce o efficiente): c'è un numero limitato di nodi che la memoria del tuo computer può contenere, diciamo N record. Se attraversi più di N record, hai un ciclo.


Questo non è O (1), questo algoritmo non ha complessità temporale significativa nella notazione big-O. La notazione O grande ti dice solo delle prestazioni nel limite quando la dimensione dell'ingresso va all'infinito. Quindi, se il tuo algoritmo si basa sul presupposto che non ci sono elenchi con più di N elementi per alcuni N grandi, il limite del tempo di esecuzione man mano che la dimensione dell'elenco si avvicina all'infinito non è definito. Quindi, la complessità non è "O (niente)".
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;
}

Utilizzare la funzione precedente per rilevare un loop nell'elenco collegato in Java.


2
Quasi uguale alla mia risposta sopra, ma ha un problema. Genererà una NullPointerException per gli elenchi con elenchi di lunghezza dispari (senza loop). Ad esempio, se head.next è null, sec.next.next genererà un NPE.
Dave L.,

1

Il rilevamento di un ciclo in un elenco collegato può essere eseguito in uno dei modi più semplici, il che si traduce in complessità O (N) usando hashmap o O (NlogN) usando un approccio basato sull'ordinamento.

Mentre attraversi l'elenco a partire dalla testa, crea un elenco ordinato di indirizzi. Quando si inserisce un nuovo indirizzo, verificare se l'indirizzo è già presente nell'elenco ordinato, il che richiede complessità O (logN).


La complessità di questo approccio è O (N log N)
fgp

0

Non riesco a vedere alcun modo per far sì che ciò richieda una quantità fissa di tempo o spazio, entrambi aumenteranno con la dimensione dell'elenco.

Vorrei utilizzare un IdentityHashMap (dato che non esiste ancora un IdentityHashSet) e memorizzare ciascun nodo nella mappa. Prima che un nodo sia memorizzato, si chiamerà contengaKey su di esso. Se il nodo esiste già, hai un ciclo.

ItentityHashMap usa == invece di .equals in modo da controllare dove si trova l'oggetto in memoria piuttosto che se ha lo stesso contenuto.


3
È certamente impossibile che richieda un determinato periodo di tempo, poiché potrebbe esserci un loop alla fine dell'elenco, quindi l'intero elenco deve essere visitato. Tuttavia, l'algoritmo Fast / Slow mostra una soluzione che utilizza una quantità fissa di memoria.
Dave L.,

Non si riferisce al suo comportamento asintotico, cioè è lineare O (n) dove n è la lunghezza della lista. Risolto il problema con O (1)
Mark Robson,

0

Potrei essere terribilmente in ritardo e nuovo per gestire questa discussione. Ma comunque ..

Perché non è possibile memorizzare l'indirizzo del nodo e il nodo "successivo" indicato in una tabella

Se potessimo tabulare in questo modo

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)

Quindi c'è un ciclo formato.


La tua soluzione non supera il requisito di "quantità costante di spazio".
Arnaud,

0

Ecco il mio codice eseguibile.

Quello che ho fatto è riverire l'elenco collegato usando tre nodi temporanei (complessità dello spazio O(1)) che tengono traccia dei collegamenti.

Il fatto interessante nel farlo è quello di aiutare a rilevare il ciclo nell'elenco collegato perché, man mano che avanzi, non ti aspetti di tornare al punto di partenza (nodo radice) e uno dei nodi temporanei dovrebbe andare a null a meno che tu non hanno un ciclo che significa che punta al nodo principale.

La complessità temporale di questo algoritmo è O(n)e la complessità spaziale è O(1).

Ecco il nodo della classe per l'elenco collegato:

public class LinkedNode{
    public LinkedNode next;
}

Ecco il codice principale con un semplice test case di tre nodi che l'ultimo nodo punta al secondo nodo:

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

Ecco un semplice test case di tre nodi che l'ultimo nodo punta al secondo nodo:

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

Questo codice è ottimizzato e produrrà risultati più rapidamente rispetto a quello scelto come risposta migliore. Questo codice salva dall'andare in un processo molto lungo di inseguimento del puntatore del nodo avanti e indietro che si verificherà nel caso seguente se seguiamo il "migliore rispondi al metodo. Osserva la sequenza a secco di quanto segue e capirai cosa sto cercando di dire, quindi osserva il problema attraverso il metodo indicato di seguito e misura il no. dei passi compiuti per trovare la risposta.

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

Ecco il codice:

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
}

Sei sicuro che questo produca il giusto risultato in tutte le situazioni? Se esegui questo algoritmo nell'elenco 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 3 -> ..., credo che restituirebbe 4 come capo, mentre tu volevi 3.
Sunreef,

La domanda è solo se esiste un ciclo o no. In questo caso, sì, la domanda funzionerà perfettamente e otterrà il risultato booleano desiderato per il caso.Se si desidera il nodo esatto da dove è iniziato il ciclo, allora lo faremo è necessario aggiungere qualcosa in più al codice, ma per quanto riguarda la produzione di un risultato, ciò produrrà una conclusione più rapida.
Sarthak Mehra,

Non hai letto correttamente la domanda: qual è il modo migliore di scrivere boolean hasLoop(Node first)che tornerebbe vero se il nodo dato è il primo di un elenco con un ciclo e falso altrimenti?
Sunreef,

Ecco la corsa a secco per l'elenco. Il primo valore indica il puntatore indietro e la seconda parte indica il puntatore avanti. (1,1) - (1,3) - (2,3) - (2,5) - (3,5) - (3,7) - (4,7) - (4,4).
Sarthak Mehra,

In realtà, mi rendo conto ora che ci sono due modi per capire la domanda (o almeno vedo due diverse interpretazioni). Il tuo algoritmo è corretto se stai solo cercando un loop. Ma ho pensato che la domanda fosse: dove stava iniziando il ciclo.
Sunreef,

0

Ecco la mia soluzione 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

Puoi usare l'algoritmo di tartaruga di Floyd come suggerito anche nelle risposte sopra.

Questo algoritmo può verificare se un elenco collegato singolarmente ha un ciclo chiuso. Ciò può essere ottenuto ripetendo un elenco con due puntatori che si sposteranno a velocità diverse. In questo modo, se esiste un ciclo, i due puntatori si incontreranno ad un certo punto in futuro.

Non esitate a consultare il mio post sul blog sulla struttura dei dati degli elenchi collegati, in cui ho anche incluso uno snippet di codice con un'implementazione dell'algoritmo sopra menzionato in linguaggio java.

Saluti,

Andreas (@xnorcode)


0

Ecco la soluzione per rilevare il ciclo.

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

// elenco collegato trova la funzione loop

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

Questo approccio ha un sovraccarico di spazio, ma un'implementazione più semplice:

Il loop può essere identificato memorizzando i nodi in una mappa. E prima di mettere il nodo; controlla se il nodo esiste già. Se il nodo esiste già nella mappa, significa che l'elenco collegato ha un ciclo.

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

Questo non soddisfa la quantità costante di restrizione di spazio indicata nella domanda!
Dedek,

concordare che ha spazio in testa; è un altro approccio per risolvere questo problema. L'approccio ovvio è l'algoritmo tartaruga e harse.
rai.skumar,

@downvoter, sarebbe utile se tu potessi spiegare anche il motivo.
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;

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.