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 true
si el nodo dado es el primero de una lista con un bucle, y de lo false
contrario? ¿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:
java
algorithm
data-structures
linked-list
jjujuma
fuente
fuente
finite amount of space and a reasonable amount of time?
:)Respuestas:
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
1
nodo y el otro por2
nodos.next
) se convertiránull
.Función Java que implementa el algoritmo:
fuente
fast.next
antes denext
volver a llamar :if(fast.next!=null)fast=fast.next.next;
Aquí hay un refinamiento de la solución Fast / Slow, que maneja correctamente las listas de longitud impar y mejora la claridad.
fuente
slow == fast.next
entoncesslow
será igualfast
en la próxima iteración; solo guarda una iteración como máximo a expensas de una prueba adicional para cada iteración.slow
no puede volverse nulo antesfast
ya 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).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.
fuente
slow.next != null
? Por lo que puedo ver,slow
siempre está detrás o igualfast
.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
first
donde termina.Código de prueba:
fuente
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:
(La mayor parte de la solución no comprueban para ambos
next
ynext.next
para los nulos Además, dado que la tortuga está siempre detrás, que no tiene que comprobar null -.. La liebre que ya hizo)fuente
El usuario unicornaddict tiene un buen algoritmo anterior, pero desafortunadamente contiene un error para las listas de longitud impar> = 3. El problema es que
fast
puede quedar "atascado" justo antes del final de la lista, loslow
alcanza y se detecta (incorrectamente) un bucle.Aquí está el algoritmo corregido.
fuente
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.
fuente
a
es mayor que la longitud del bucle, entonces rápido hará un bucle múltiple y la fórmuladistance (fast) = a + b + b + c
cambiará paraa + (b+c) * k + b
introducir un parámetro adicionalk
que cuente el número de bucles realizados por uno rápido.Algoritmo
Complejidad
fuente
n
, arregladoequals
yhashCode
. No es lo mismo. Y desreferencianull
en el último elemento. Y la pregunta no decía nada sobre almacenar los nodos en aLinkedList
.El siguiente puede no ser el mejor método: es O (n ^ 2). Sin embargo, debería servir para hacer el trabajo (eventualmente).
fuente
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.
fuente
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 decounter
. ¿Parece esto una solución adecuada? ¿O hay una manera de hacerlo sin incrustarNode
? (Obviamente, en una implementación real habría más métodos, comoRemoveNode(Node n)
, etc.)fuente
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.
fuente
fuente
Utilice la función anterior para detectar un bucle en la lista vinculada en java.
fuente
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).
fuente
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.
fuente
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
Por lo tanto, se forma un ciclo.
fuente
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 esO(1)
.Aquí está el nodo de clase para la lista vinculada:
Aquí está el código principal con un caso de prueba simple de tres nodos que el último nodo apunta al segundo nodo:
Aquí está el caso de prueba simple de tres nodos que el último nodo apunta al segundo nodo:
fuente
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:
fuente
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?Aquí está mi solución en java
fuente
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)
fuente
Aquí está la solución para detectar el ciclo.
fuente
// función de bucle de búsqueda de lista vinculada
fuente
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.
fuente
fuente