¿Cómo detectar un bucle en una lista vinculada?


434

Digamos que tiene una estructura de lista vinculada en Java. Se compone de nodos:

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

y cada nodo apunta al siguiente nodo, excepto el último nodo, que tiene un valor nulo para el siguiente. Digamos que existe la posibilidad de que la lista pueda contener un bucle, es decir, el Nodo final, en lugar de tener un valor nulo, tiene una referencia a uno de los nodos en la lista que vino antes.

¿Cuál es la mejor forma de escribir?

boolean hasLoop(Node first)

¿cuál volvería truesi el nodo dado es el primero de una lista con un bucle, y de lo falsecontrario? ¿Cómo podrías escribir para que ocupe una cantidad constante de espacio y una cantidad razonable de tiempo?

Aquí hay una imagen de cómo se ve una lista con un bucle:

texto alternativo


50
Wow..I encantaría trabajar para este empleador finite amount of space and a reasonable amount of time?:)
codaddict

10
@SLaks: el bucle no necesariamente regresa al primer nodo. Puede regresar a la mitad.
jjujuma

109
Vale la pena leer las respuestas a continuación, pero las preguntas de entrevista como esta son terribles. Usted sabe la respuesta (es decir, ha visto una variante en el algoritmo de Floyd) o no, y no hace nada para probar su capacidad de razonamiento o diseño.
GaryF

3
Para ser justos, la mayoría de los "algoritmos de conocimiento" son así, ¡a menos que esté haciendo cosas de nivel de investigación!
Larry

12
@GaryF Y, sin embargo, sería revelador saber qué harían cuando no supieran la respuesta. Por ejemplo, ¿qué pasos tomarían, con quién trabajarían, qué harían para superar la falta de conocimiento de algoritmos?
Chris Knight

Respuestas:


538

Puede utilizar el algoritmo de búsqueda de ciclos de Floyd , también conocido como algoritmo de tortuga y liebre .

La idea es tener dos referencias a la lista y moverlas a diferentes velocidades . Mueva uno hacia adelante por 1nodo y el otro por 2nodos.

  • Si la lista vinculada tiene un bucle, definitivamente se encontrarán.
  • De lo contrario, cualquiera de las dos referencias (o sus next) se convertirá null.

Función Java que implementa el 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
También es necesario hacer una comprobación nula fast.nextantes de nextvolver a llamar :if(fast.next!=null)fast=fast.next.next;
cmptrgeekken

12
debe verificar no solo (lento == rápido) sino: (lento == rápido || lento.siguiente == rápido) para evitar saltar el rápido sobre el lento
Oleg Razgulyaev

13
Me equivoqué: rápido no puede saltar lento, porque saltar lento en el siguiente paso rápido debería tener la misma posición que lento :)
Oleg Razgulyaev

44
La comprobación de slow == null es redundante a menos que la lista tenga solo un nodo. También puede deshacerse de una llamada a Node.next. Aquí hay una versión más simple y rápida del bucle: pastie.org/927591
Kay Sarraute

22
Realmente deberías citar tus referencias. Este algoritmo fue inventado por Robert Floyd en los años 60, es conocido como el algoritmo de búsqueda de ciclo de Floyd, también conocido como. El algoritmo de tortuga y liebre.
joshperry

127

Aquí hay un refinamiento de la solución Fast / Slow, que maneja correctamente las listas de longitud impar y mejora la claridad.

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
Agradable y sucinto. Este código se puede optimizar comprobando si lento == rápido || (fast.next! = null && slow = fast.next); :)
arachnode.net

11
@ arachnode.net Eso no es una optimización. Si slow == fast.nextentonces slowserá igual fasten la próxima iteración; solo guarda una iteración como máximo a expensas de una prueba adicional para cada iteración.
Jason C

@ ana01 slowno puede volverse nulo antes fastya que sigue la misma ruta de referencias (a menos que tenga una modificación concurrente de la lista en cuyo caso todas las apuestas están desactivadas).
Dave L.

Por curiosidad, ¿cómo funciona esto para números impares? ¿Todavía no puedo pasar a la tortuga en listas enlazadas de longitud impar?
theGreenCabbage

1
@theGreenCabbage Cada iteración del bucle de la liebre va un paso más adelante que la tortuga. Entonces, si la liebre está detrás en 3 pasos, entonces la siguiente iteración toma dos saltos y la tortuga da un salto, y ahora la liebre está detrás en 2 pasos. Después de la siguiente iteración, la liebre está detrás por 1 salto, y luego queda atrapada exactamente. Si la liebre tomó 3 saltos mientras que la tortuga tomó uno, entonces podría saltar porque ganaría 2 cada vez, pero dado que solo gana 1 por cada iteración, no puede saltar.
Dave L.

52

Mejor que el algoritmo de Floyd

Richard Brent describió un algoritmo de detección de ciclo alternativo , que es muy parecido a la liebre y la tortuga [ciclo de Floyd], excepto que el nodo lento aquí no se mueve, pero luego es "teletransportado" a la posición del nodo rápido en fijo intervalos.

La descripción está disponible aquí: http://www.siafoo.net/algorithm/11 Brent afirma que su algoritmo es 24 a 36% más rápido que el algoritmo de ciclo de Floyd. O (n) complejidad temporal, O (1) complejidad espacial.

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

¡Esta respuesta es asombrosa!
valin077

1
Realmente me gustó su respuesta, la incluí en mi blog: k2code.blogspot.in/2010/04/… .
kinshuk4

¿Por qué necesitas verificar slow.next != null? Por lo que puedo ver, slowsiempre está detrás o igual fast.
TWiStErRob

Lo hice hace mucho tiempo, cuando comencé a aprender algoritmos. Editado el código. Gracias :)
Ashok Bijoy Debnath

50

Una solución alternativa a la tortuga y el conejo, no tan agradable, ya que temporalmente cambio la lista:

La idea es recorrer la lista y revertirla a medida que avanza. Luego, cuando llegue por primera vez a un nodo que ya ha sido visitado, su siguiente puntero apuntará "hacia atrás", haciendo que la iteración avance hacia firstdonde 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;

Código de prueba:

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

¿Funciona correctamente el reverso cuando el bucle apunta a cualquier nodo que no sea el primero? Si la lista vinculada inicial es así 1-> 2-> 3-> 4-> 5-> 2 (con un ciclo de 5 a 2), la lista invertida se ve como 1-> 2 <-3 <-4 <-5? ¿Y si lo contrario es eso, la lista reconstruida final se arruinará?
Zenil

1
@Zenil: Por eso escribí ese último caso de prueba, donde el último nodo apunta al centro de la lista. Si la reconstrucción no funcionara, esa prueba fallaría. Acerca de su ejemplo: la inversión de 1-> 2-> 3-> 5-> 2 sería 1-> 2-> 5-> 4-> 3-> 2, porque el ciclo solo se detiene una vez al final de la lista se ha alcanzado, no cuando se ha alcanzado el final del ciclo (que no podemos detectar fácilmente).
meriton

28

Tortuga y liebre

Echa un vistazo al algoritmo rho de Pollard . No es exactamente el mismo problema, pero tal vez comprenda la lógica de él y lo aplique a las listas vinculadas.

(si eres flojo, puedes verificar la detección del ciclo , verifica la parte sobre la tortuga y la liebre).

Esto solo requiere tiempo lineal y 2 punteros adicionales.

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 mayor parte de la solución no comprueban para ambos nexty next.nextpara los nulos Además, dado que la tortuga está siempre detrás, que no tiene que comprobar null -.. La liebre que ya hizo)


13

El usuario unicornaddict tiene un buen algoritmo anterior, pero desafortunadamente contiene un error para las listas de longitud impar> = 3. El problema es que fastpuede quedar "atascado" justo antes del final de la lista, lo slowalcanza y se detecta (incorrectamente) un bucle.

Aquí está el algoritmo corregido.

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

En este contexto, hay cargas de materiales textuales en todas partes. Solo quería publicar una representación esquemática que realmente me ayudó a comprender el concepto.

Cuando rápido y lento se encuentran en el punto p,

Distancia recorrida por rápido = a + b + c + b = a + 2b + c

Distancia recorrida por lento = a + b

Dado que el rápido es 2 veces más rápido que el lento. Entonces a + 2b + c = 2 (a + b) , entonces obtenemos a = c .

Entonces, cuando otro puntero lento vuelve a correr de la cabeza a la q , al mismo tiempo, el puntero rápido corre de p a q , por lo que se encuentran en el punto q juntos.

ingrese la descripción de la imagen aquí

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
Una imagen vale más que miles de palabras. ¡Gracias por la explicación clara y simple!
Calios

1
La mejor explicación en internet. Solo agregaría que esto prueba que el puntero rápido y lento converge después del tiempo lineal
VarunPandey

si aes mayor que la longitud del bucle, entonces rápido hará un bucle múltiple y la fórmula distance (fast) = a + b + b + ccambiará para a + (b+c) * k + bintroducir un parámetro adicional kque cuente el número de bucles realizados por uno rápido.
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;
}

Complejidad

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

¿Cómo es la complejidad espacial O (2n)?
Programador345

@ user3543449 tienes razón, debería ser justo n, arreglado
Khaled.K

1
Este es realmente el tiempo ~ O (n ^ 2) ya que cada uno contiene un cheque para una ArrayList toma O (n) y hay O (n) de ellos. Utilice un HashSet en su lugar para el tiempo lineal.
Dave L.

3
Esto no prueba los ciclos sino los valores duplicados usando los elementos equalsy hashCode. No es lo mismo. Y desreferencia nullen el último elemento. Y la pregunta no decía nada sobre almacenar los nodos en a LinkedList.
Lii

2
@Lii es un pseudocódigo, no es un código Java, por eso lo
titulé

8

El siguiente puede no ser el mejor método: es O (n ^ 2). Sin embargo, debería servir para hacer el trabajo (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++;
}

¿Cómo sabrías cuántos elementos hay en la lista para hacer for ()?
Jethro Larson

@JethroLarson: el último nodo en una lista vinculada apunta a una dirección conocida (en muchas implementaciones, esto es NULL). Termine el ciclo for cuando se alcanza esa dirección conocida.
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;
}

Perdóname mi ignorancia (todavía soy bastante nuevo en Java y programación), pero ¿por qué no funcionaría lo anterior?

Supongo que esto no resuelve el problema del espacio constante ... pero al menos llega allí en un tiempo razonable, ¿correcto? Solo ocupará el espacio de la lista vinculada más el espacio de un conjunto con n elementos (donde n es el número de elementos en la lista vinculada, o el número de elementos hasta que llegue a un bucle). Y por el momento, el análisis del peor de los casos, creo, sugeriría O (nlog (n)). Las búsquedas de SortedSet para contienen () son log (n) (verifique el javadoc, pero estoy bastante seguro de que la estructura subyacente de TreeSet es TreeMap, que a su vez es un árbol rojo-negro), y en el peor de los casos (sin bucles, o bucle al final), tendrá que hacer n búsquedas.


2
Sí, una solución con algún tipo de Set funciona bien, pero requiere un espacio proporcional al tamaño de la lista.
jjujuma

3

Si se nos permite incrustar la clase Node, resolvería el problema tal como lo he implementado a continuación. hasLoop()se ejecuta en O (n) tiempo y solo ocupa el espacio de counter. ¿Parece esto una solución adecuada? ¿O hay una manera de hacerlo sin incrustar Node? (Obviamente, en una implementación real habría más métodos, como 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

Incluso podría hacerlo en un tiempo O (1) constante (aunque no sería muy rápido o eficiente): hay una cantidad limitada de nodos que la memoria de su computadora puede contener, digamos N registros. Si atraviesa más de N registros, entonces tiene un bucle.


Esto no es O (1), este algoritmo no tiene una complejidad de tiempo significativa en la notación big-O. La notación O grande solo le informa sobre el rendimiento en el límite a medida que el tamaño de entrada llega al infinito. Así que si su algoritmo se basa en la suposición de que no hay ninguna lista con más de N elementos de algunas N grande, el límite del tiempo de ejecución como el tamaño de la lista tiende a infinito es indefinido. Por lo tanto, la complejidad no es "O (nada)".
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;
}

Utilice la función anterior para detectar un bucle en la lista vinculada en java.


2
Casi lo mismo que mi respuesta anterior, pero tiene un problema. Lanzará una NullPointerException para listas con listas de longitud impar (sin bucles). Por ejemplo, si head.next es nulo, sec.next.next arrojará un NPE.
Dave L.

1

La detección de un bucle en una lista vinculada se puede hacer de una de las formas más simples, lo que resulta en la complejidad de O (N) usando hashmap u O (NlogN) usando un enfoque basado en la ordenación.

A medida que recorre la lista desde la cabecera, cree una lista ordenada de direcciones. Cuando inserte una nueva dirección, verifique si la dirección ya está allí en la lista ordenada, lo que requiere complejidad O (logN).


La complejidad de este apporach es O (N log N)
fgp

0

No veo ninguna forma de hacer que esto tome una cantidad fija de tiempo o espacio, ambos aumentarán con el tamaño de la lista.

Haría uso de un IdentityHashMap (dado que aún no existe un IdentityHashSet) y almacenaría cada Nodo en el mapa. Antes de que se almacene un nodo, debe llamar a usesKey en él. Si el nodo ya existe, tiene un ciclo.

ItentityHashMap usa == en lugar de .equals para que esté verificando dónde está el objeto en la memoria en lugar de si tiene el mismo contenido.


3
Ciertamente es imposible que tome un tiempo fijo, ya que podría haber un bucle al final de la lista, por lo que debe visitarse toda la lista. Sin embargo, el algoritmo Fast / Slow demuestra una solución que usa una cantidad fija de memoria.
Dave L.

¿No se refiere a su comportamiento asintótico, es decir, es O lineal (n) donde n es la longitud de la lista. Solucionado sería O (1)
Mark Robson

0

Podría llegar terriblemente tarde y nuevo para manejar este hilo. Pero aún..

¿Por qué no se puede almacenar la dirección del nodo y el nodo "siguiente" señalado en una tabla

Si pudiéramos tabular de esta manera

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)

Por lo tanto, se forma un ciclo.


Su solución no supera el requisito de "cantidad constante de espacio".
Arnaud

0

Aquí está mi código ejecutable.

Lo que he hecho es reverenciar la lista vinculada mediante el uso de tres nodos temporales (complejidad del espacio O(1)) que realizan un seguimiento de los enlaces.

El hecho interesante de hacerlo es ayudar a detectar el ciclo en la lista vinculada porque, a medida que avanza, no espera volver al punto de partida (nodo raíz) y uno de los nodos temporales debería ir a nulo a menos que usted tener un ciclo que significa que apunta al nodo raíz.

La complejidad temporal de este algoritmo es O(n)y la complejidad espacial es O(1).

Aquí está el nodo de clase para la lista vinculada:

public class LinkedNode{
    public LinkedNode next;
}

Aquí está el código principal con un caso de prueba simple de tres nodos que el último nodo apunta al segundo 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;
    }

Aquí está el caso de prueba simple de tres nodos que el último nodo apunta al segundo 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

Este código está optimizado y producirá un resultado más rápido que con el elegido como la mejor respuesta. Este código evita entrar en un proceso muy largo de perseguir el puntero de nodo hacia adelante y hacia atrás que ocurrirá en el siguiente caso si seguimos el 'mejor respuesta '. Mire a través de la ejecución en seco de lo siguiente y se dará cuenta de lo que estoy tratando de decir. Luego mire el problema a través del método dado a continuación y mida el no. de pasos dados para encontrar la respuesta.

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

Aquí está el código:

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
}

¿Estás seguro de que esto produce el resultado correcto en todas las situaciones? Si ejecuta este algoritmo en la lista 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 3 -> ..., creo que devolvería 4 como la cabeza, mientras que usted quería 3.
Sunreef

La pregunta es encontrar si existe un bucle o no. En este caso, sí, la pregunta funcionará absolutamente bien y obtendrá el resultado booleano deseado para el caso. Si desea el nodo exacto desde donde comenzó el bucle, lo haremos necesita agregar algo más al código. Pero en lo que respecta a producir un resultado, esto producirá una conclusión más rápida.
Sarthak Mehra

No leyó la pregunta correctamente: ¿Cuál es la mejor manera de escribir boolean hasLoop(Node first)que devolvería verdadero si el Nodo dado es el primero de una lista con un bucle, y falso de lo contrario?
Sunreef

Aquí está la ejecución en seco para su lista. El primer valor significa puntero hacia atrás y la segunda parte significa puntero hacia adelante. (1,1) - (1,3) - (2,3) - (2,5) - (3,5) - (3,7) - (4,7) - (4,4).
Sarthak Mehra

En realidad, ahora me doy cuenta de que hay dos formas de entender la pregunta (o al menos veo dos interpretaciones diferentes). Su algoritmo es correcto si solo está buscando si hay un bucle. Pero pensé que la pregunta era dónde estaba comenzando el ciclo.
Sunreef

0

Aquí está mi solución 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

También puede usar el algoritmo de tortuga de Floyd como se sugiere en las respuestas anteriores.

Este algoritmo puede verificar si una lista vinculada individualmente tiene un ciclo cerrado. Esto se puede lograr iterando una lista con dos punteros que se moverán a una velocidad diferente. De esta manera, si hay un ciclo, los dos punteros se encontrarán en algún momento en el futuro.

Por favor, siéntase libre de revisar mi blog en la estructura de datos de las listas vinculadas, donde también incluí un fragmento de código con una implementación del algoritmo mencionado anteriormente en lenguaje java.

Saludos,

Andreas (@xnorcode)


0

Aquí está la solución para detectar el 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

// función de bucle de búsqueda de lista vinculada

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

Este enfoque tiene sobrecarga de espacio, pero una implementación más simple:

El bucle se puede identificar almacenando nodos en un mapa. Y antes de poner el nodo; compruebe si el nodo ya existe. Si el nodo ya existe en el mapa, significa que la Lista vinculada tiene un bucle.

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

¡Esto no cumple con la cantidad constante de restricción de espacio dada en la pregunta!
dedek

Estoy de acuerdo en que tiene espacio encima; Es otro enfoque para resolver este problema. El enfoque obvio es la tortuga y el algoritmo de ataque.
rai.skumar

@downvoter, sería útil si pudieras explicar el motivo también.
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;

}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.