Digamos que você tenha uma estrutura de lista vinculada em Java. É composto de nós:
class Node {
Node next;
// some user data
}
e cada Nó aponta para o próximo nó, exceto o último Nó, que é nulo para o próximo. Digamos que exista a possibilidade de a lista conter um loop - ou seja, o Nó final, em vez de ter um nulo, tem uma referência a um dos nós da lista que vieram antes dela.
Qual é a melhor maneira de escrever
boolean hasLoop(Node first)
que retornaria true
se o nó fornecido fosse o primeiro de uma lista com um loop e false
caso contrário? Como você pode escrever para ocupar uma quantidade constante de espaço e uma quantidade razoável de tempo?
Aqui está uma imagem de como é uma lista com um loop:
java
algorithm
data-structures
linked-list
jjujuma
fonte
fonte
finite amount of space and a reasonable amount of time?
:)Respostas:
Você pode usar o algoritmo de descoberta de ciclo de Floyd , também conhecido como algoritmo de tartaruga e lebre .
A idéia é ter duas referências à lista e movê-las em velocidades diferentes . Avance um por
1
nó e o outro por2
nós.next
) se tornaránull
.Função Java que implementa o algoritmo:
fonte
fast.next
antes de chamarnext
novamente:if(fast.next!=null)fast=fast.next.next;
Aqui está um refinamento da solução Fast / Slow, que lida corretamente com listas de tamanhos ímpares e melhora a clareza.
fonte
slow == fast.next
entãoslow
será igualfast
na próxima iteração; ele salva apenas uma iteração, no máximo, à custa de um teste adicional para cada iteração.slow
não pode se tornar nulo antesfast
, pois segue o mesmo caminho de referências (a menos que você tenha uma modificação simultânea da lista, caso em que todas as apostas estão desativadas).Melhor que o algoritmo de Floyd
Richard Brent descreveu um algoritmo de detecção de ciclo alternativo , que é muito parecido com a lebre e a tartaruga [ciclo de Floyd], exceto que o nó lento aqui não se move, mas depois é "teleportado" para a posição do nó rápido no ponto fixo. intervalos.
A descrição está disponível aqui: http://www.siafoo.net/algorithm/11 Brent afirma que seu algoritmo é 24 a 36% mais rápido que o algoritmo de ciclo de Floyd. O (n) complexidade do tempo, O (1) complexidade do espaço.
fonte
slow.next != null
? Tanto quanto eu posso ver,slow
está sempre atrasado ou igual afast
.Uma solução alternativa para a tartaruga e o coelho, não tão agradável quanto eu altero temporariamente a lista:
A idéia é percorrer a lista e inverter a medida que você avança. Então, quando você alcançar um nó que já foi visitado pela primeira vez, seu próximo ponteiro apontará "para trás", fazendo com que a iteração prossiga
first
novamente, onde termina.Código do teste:
fonte
Tartaruga e lebre
Dê uma olhada no algoritmo rho de Pollard . Não é exatamente o mesmo problema, mas talvez você entenda a lógica e a aplique em listas vinculadas.
(se você é preguiçoso, basta verificar a detecção de ciclo - verifique a parte sobre a tartaruga e a lebre.)
Isso requer apenas tempo linear e 2 indicadores extras.
Em Java:
(A maioria da solução não verifica os dois
next
e osnext.next
nulos. Além disso, como a tartaruga está sempre atrasada, você não precisa verificar se é nulo - a lebre já fez isso.)fonte
O usuário unicornaddict possui um bom algoritmo acima, mas, infelizmente, contém um erro para listas não-loops de comprimento estranho> = 3. O problema é que ele
fast
pode "ficar preso" pouco antes do final da lista,slow
alcançá-lo e um loop é (erroneamente) detectado.Aqui está o algoritmo corrigido.
fonte
Nesse contexto, existem muitos materiais textuais em todos os lugares. Eu só queria postar uma representação diagramática que realmente me ajudou a entender o conceito.
Quando rápido e lento se encontram no ponto p,
Distância percorrida rapidamente = a + b + c + b = a + 2b + c
Distância percorrida lentamente = a + b
Como o jejum é 2 vezes mais rápido que o lento. Então a + 2b + c = 2 (a + b) , então obtemos a = c .
Portanto, quando outro ponteiro lento é executado novamente da cabeça para q , ao mesmo tempo, o ponteiro rápido é executado de p para q , então eles se encontram no ponto q juntos.
fonte
a
for maior que o comprimento do loop, o fast fará vários loop e a fórmuladistance (fast) = a + b + b + c
mudará paraa + (b+c) * k + b
introduzir parâmetros extrask
que contam o número de lopps feitos pelo fast.Algoritmo
Complexidade
fonte
n
, fixaequals
ehashCode
. Não é a mesma coisa. E desreferencianull
no último elemento. E a pergunta não disse nada sobre como armazenar os nós em um arquivoLinkedList
.O seguinte pode não ser o melhor método - é O (n ^ 2). No entanto, deve servir para fazer o trabalho (eventualmente).
fonte
Perdoe minha ignorância (ainda sou relativamente novo em Java e programação), mas por que as opções acima não funcionavam?
Eu acho que isso não resolve o problema constante de espaço ... mas pelo menos chega lá em um tempo razoável, correto? Ele ocupará apenas o espaço da lista vinculada mais o espaço de um conjunto com n elementos (em que n é o número de elementos na lista vinculada ou o número de elementos até que ele atinja um loop). E, por algum tempo, a análise do pior caso, eu acho, sugeriria O (nlog (n)). As pesquisas SortedSet para contains () são log (n) (verifique o javadoc, mas tenho certeza de que a estrutura subjacente do TreeSet é o TreeMap, que por sua vez é uma árvore vermelha e preta) e, na pior das hipóteses (sem loops, ou loop no final), ele terá que fazer n pesquisas.
fonte
Se for permitido incorporar a classe
Node
, eu resolveria o problema como o implementei abaixo.hasLoop()
roda em O (n) tempo e ocupa apenas o espaço decounter
. Parece uma solução apropriada? Ou existe uma maneira de fazer isso sem incorporarNode
? (Obviamente, em uma implementação real, haveria mais métodos, comoRemoveNode(Node n)
etc.)fonte
Você pode até fazer isso em tempo O (1) constante (embora não seja muito rápido ou eficiente): existe uma quantidade limitada de nós que a memória do computador pode conter, digamos, N registros. Se você percorrer mais de N registros, terá um loop.
fonte
fonte
Use a função acima para detectar um loop na lista vinculada em java.
fonte
A detecção de um loop em uma lista vinculada pode ser feita de uma das maneiras mais simples, o que resulta na complexidade de O (N) usando hashmap ou O (NlogN) usando uma abordagem baseada em classificação.
À medida que você percorre a lista começando do início, crie uma lista classificada de endereços. Ao inserir um novo endereço, verifique se o endereço já existe na lista classificada, o que exige complexidade O (logN).
fonte
Não vejo nenhuma maneira de fazer isso levar uma quantidade fixa de tempo ou espaço; ambos aumentarão com o tamanho da lista.
Eu usaria um IdentityHashMap (já que ainda não há um IdentityHashSet) e armazenaria cada Nó no mapa. Antes de um nó ser armazenado, você chamaria containsKey nele. Se o nó já existir, você terá um ciclo.
O ItentityHashMap usa == em vez de .equals, para que você verifique onde o objeto está na memória e não se ele possui o mesmo conteúdo.
fonte
Eu posso estar terrivelmente atrasado e novo para lidar com esse tópico. Mas ainda..
Por que o endereço do nó e o nó "próximo" apontado não podem ser armazenados em uma tabela
Se pudéssemos tabular dessa maneira
Portanto, há um ciclo formado.
fonte
Aqui está o meu código executável.
O que fiz foi reverenciar a lista vinculada usando três nós temporários (complexidade do espaço
O(1)
) que controlam os links.O fato interessante de fazer isso é ajudar a detectar o ciclo na lista vinculada, pois à medida que você avança, não espera voltar ao ponto inicial (nó raiz) e um dos nós temporários deve ir para nulo, a menos que tem um ciclo, o que significa que aponta para o nó raiz.
A complexidade do tempo deste algoritmo é
O(n)
e a complexidade do espaço éO(1)
.Aqui está o nó da classe para a lista vinculada:
Aqui está o código principal com um caso de teste simples de três nós que o último nó apontando para o segundo nó:
Aqui está um caso de teste simples de três nós que o último nó apontando para o segundo nó:
fonte
Esse código é otimizado e produzirá resultados mais rapidamente do que o escolhido como a melhor resposta. Esse código evita o processo muito longo de perseguir o ponteiro de nó para frente e para trás, que ocorrerá no caso a seguir, se seguirmos o 'melhor responda '. Observe o procedimento a seguir e você perceberá o que estou tentando dizer. Depois, observe o problema pelo método abaixo e meça o não. das medidas tomadas para encontrar a resposta.
1-> 2-> 9-> 3 ^ -------- ^
Aqui está o código:
fonte
boolean hasLoop(Node first)
que retornaria true se o Nó fornecido for o primeiro de uma lista com um loop e false caso contrário?Aqui está a minha solução em java
fonte
Você também pode usar o algoritmo de tartaruga de Floyd, como sugerido nas respostas acima.
Esse algoritmo pode verificar se uma lista vinculada individualmente possui um ciclo fechado. Isso pode ser alcançado iterando uma lista com dois ponteiros que se moverão em velocidade diferente. Dessa forma, se houver um ciclo, os dois indicadores se encontrarão em algum momento no futuro.
Por favor, sinta-se livre para conferir meu blog no na estrutura de dados das listas vinculadas, onde também incluí um trecho de código com uma implementação do algoritmo mencionado acima na linguagem java.
Saudações,
Andreas (@xnorcode)
fonte
Aqui está a solução para detectar o ciclo.
fonte
// função de loop de localização de lista vinculada
fonte
Essa abordagem possui sobrecarga de espaço, mas uma implementação mais simples:
O loop pode ser identificado armazenando nós em um mapa. E antes de colocar o nó; verifique se o nó já existe. Se o nó já existir no mapa, significa que a Lista vinculada tem loop.
fonte
fonte