Como detectar um loop em uma lista vinculada?

434

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 truese o nó fornecido fosse o primeiro de uma lista com um loop e falsecaso 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:

texto alternativo

jjujuma
fonte
50
Wow..I adoraria trabalhar para este empregador finite amount of space and a reasonable amount of time?:)
codaddict
10
@SLaks - o loop não é necessário loop de volta ao primeiro nó. Pode voltar à metade.
jjujuma
109
Vale a pena ler as respostas abaixo, mas perguntas como esta são terríveis. Você conhece a resposta (ou seja, você viu uma variante do algoritmo de Floyd) ou não, e isso não faz nada para testar sua capacidade de raciocínio ou design.
GaryF
3
Para ser justo, a maioria dos "algoritmos de conhecimento" é assim - a menos que você esteja fazendo coisas no nível da pesquisa!
Larry
12
@ GaryF E, no entanto, seria revelador saber o que eles fariam quando não soubessem a resposta. Por exemplo, que medidas eles tomariam, com quem trabalhariam, o que fariam para superar a falta de conhecimento sobre algoritmos?
Chris Knight

Respostas:

538

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 1nó e o outro por 2nós.

  • Se a lista vinculada tiver um loop, eles definitivamente se encontrarão.
  • Caso contrário, qualquer uma das duas referências (ou as suas next) se tornará null.

Função Java que implementa o 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;
    }
}
codaddict
fonte
29
Também precisa fazer um nulo-check na fast.nextantes de chamar nextnovamente:if(fast.next!=null)fast=fast.next.next;
cmptrgeekken
12
você deve verificar não só (lento == rápido), mas: (lento == rápido || slow.next == rápido) para evitar saltar o jejum durante o lento
Oleg Razgulyaev
13
eu estava errado: rápido não pode saltar sobre lento, porque para saltar sobre lento na próxima rápido passo deve tem os mesmos pos como lento :)
Oleg Razgulyaev
4
A verificação de slow == null é redundante, a menos que a lista tenha apenas um nó. Você também pode se livrar de uma chamada para Node.next. Aqui está uma versão mais simples e mais rápido do loop: pastie.org/927591
Kay Sarraute
22
Você realmente deve citar suas referências. Este algoritmo foi inventado por Robert Floyd nos anos 60. É conhecido como algoritmo de descoberta de ciclos de Floyd, também conhecido como. O algoritmo da tartaruga e da lebre.
joshperry
127

Aqui está um refinamento da solução Fast / Slow, que lida corretamente com listas de tamanhos ímpares e melhora a clareza.

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
}
Dave L.
fonte
2
Agradável e sucinto. Este código pode ser otimizado verificando se lento == rápido || (fast.next! = null && slow = fast.next); :)
arachnode.net
11
@ arachnode.net Isso não é uma otimização. Se slow == fast.nextentão slowserá igual fastna próxima iteração; ele salva apenas uma iteração, no máximo, à custa de um teste adicional para cada iteração.
Jason C
@ ana01 slownão pode se tornar nulo antes fast, 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).
Dave L.
Por curiosidade, como isso funciona para números ímpares? A lebre ainda não pode passar a tartaruga em listas encadeadas de tamanhos ímpares?
theGreenCabbage
1
@theGreenCabbage Cada iteração do loop da lebre fica um passo à frente da tartaruga. Portanto, se a lebre estiver atrasada em 3 passos, na próxima iteração serão necessários dois saltos e a tartaruga dará um salto, e agora a lebre estará atrasada em 2 etapas. Após a próxima iteração, a lebre fica para trás em 1 salto e é capturada exatamente. Se a lebre deu 3 saltos enquanto a tartaruga tomou um, então poderia pular, pois ganharia 2 por vez, mas como só ganha 1 por cada iteração, não pode pular.
Dave L.
52

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.

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;
}
Ashok Bijoy Debnath
fonte
Esta resposta é incrível!
valin077
1
Gostei muito da sua resposta, incluí-a no meu blog - k2code.blogspot.in/2010/04/… .
kinshuk4
Por que você precisa verificar slow.next != null? Tanto quanto eu posso ver, slowestá sempre atrasado ou igual a fast.
TWIStErRob
Eu fiz isso há muito tempo, quando comecei a aprender algoritmos. Editou o código. Obrigado :)
Ashok Bijoy Debnath
50

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 firstnovamente, onde 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 do teste:

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);
}
Meriton
fonte
O inverso funciona corretamente quando o loop está apontando para outro nó que não o primeiro? Se a lista vinculada inicial for assim 1-> 2-> 3-> 4-> 5-> 2 (com um ciclo de 5 a 2), a lista invertida será igual a 1-> 2 <-3 <-4 <-5? E se o contrário, a lista final reconstruída será estragada?
Zenil
1
@ Zenil: Foi por isso que escrevi o último caso de teste, onde o último nó aponta para o meio da lista. Se a reconstrução não funcionasse, esse teste falharia. Sobre o seu exemplo: a reversão de 1-> 2-> 3-> 5-> 2 seria 1-> 2-> 5-> 4-> 3-> 2, porque o loop é interrompido apenas quando o final da lista foi atingido, não quando o final do loop (que não conseguimos detectar facilmente) foi atingido.
meriton
28

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:

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

(A maioria da solução não verifica os dois nexte os next.nextnulos. Além disso, como a tartaruga está sempre atrasada, você não precisa verificar se é nulo - a lebre já fez isso.)

Larry
fonte
13

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 fastpode "ficar preso" pouco antes do final da lista, slowalcançá-lo e um loop é (erroneamente) detectado.

Aqui está o algoritmo corrigido.

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;
    }
}
Carl Mäsak
fonte
10

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.

insira a descrição da imagem aqui

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;
}
Neil
fonte
2
Uma imagem vale mais que milhares de palavras. Obrigado pela explicação pura e simples!
Calios
1
Melhor explicação na internet. Gostaria apenas de acrescentar que isto prova que convergem ponteiro rápido e lento após o tempo linear
VarunPandey
se afor maior que o comprimento do loop, o fast fará vários loop e a fórmula distance (fast) = a + b + b + cmudará para a + (b+c) * k + bintroduzir parâmetros extras kque contam o número de lopps feitos pelo fast.
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;
}

Complexidade

Time ~ O(n)
Space ~ O(n)
Khaled.K
fonte
Como é a complexidade do espaço O (2n)?
Programmer345
@ user3543449 você está certo, deve-se apenas n, fixa
Khaled.K
1
Este é realmente o tempo ~ O (n ^ 2), pois cada um contém a verificação de um ArrayList pega O (n) e há O (n) deles. Use um HashSet em vez do tempo linear.
Dave L.
3
Isso não testa ciclos, mas sim valores duplicados usando os elementos equalse hashCode. Não é a mesma coisa. E desreferencia nullno último elemento. E a pergunta não disse nada sobre como armazenar os nós em um arquivo LinkedList.
Lii
2
@Lii é um pseudo-código, isso não é um código Java, é por isso que eu intitulei-o com Algorithm
Khaled.K
8

O seguinte pode não ser o melhor método - é O (n ^ 2). No entanto, deve servir para fazer o trabalho (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++;
}
Sparky
fonte
Como você saberia quantos elementos estão na lista para fazer o for ()?
Jethro Larson
@JethroLarson: O último nó em uma lista vinculada aponta para um endereço conhecido (em muitas implementações, isso é NULL). Encerre o loop for quando esse endereço conhecido for atingido.
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;
}

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.

smessing
fonte
2
Sim, uma solução com algum tipo de conjunto funciona bem, mas requer espaço proporcional ao tamanho da lista.
Jjujuma
3

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 de counter. Parece uma solução apropriada? Ou existe uma maneira de fazer isso sem incorporar Node? (Obviamente, em uma implementação real, haveria mais 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;
        ....
    }

}
smessing
fonte
1

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.

Eduardo
fonte
Isso não é O (1), esse algoritmo não possui complexidade de tempo significativa na notação big-O. A notação grande-O informa apenas sobre o desempenho no limite à medida que o tamanho da entrada chega ao infinito. Portanto, se seu algoritmo baseia-se no pressuposto de que não são nenhuma lista com mais de N elementos para algum grande N, o limite do tempo de execução, como o tamanho da lista se aproxima do infinito é indefinido. Portanto, a complexidade não é "O (qualquer coisa)".
fgp 4/09/19
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;
        }
    }
}
Richa
fonte
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;
}

Use a função acima para detectar um loop na lista vinculada em java.

Aditya Parmar
fonte
2
Quase o mesmo que a minha resposta acima, mas tem um problema. Ele lançará um NullPointerException para listas com listas de tamanho ímpar (sem loops). Por exemplo, se head.next for nulo, o sec.next.next lançará um NPE.
Dave L.
1

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).

Abhinav
fonte
A complexidade desse apporach é O (N log N)
fgp 04/04/19
0

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.

TofuBeer
fonte
3
Certamente é impossível levar um tempo fixo, pois pode haver um loop no final da lista; portanto, toda a lista deve ser visitada. No entanto, o algoritmo Fast / Slow demonstra uma solução usando uma quantidade fixa de memória.
Dave L.
Não está se referindo ao seu comportamento assintótico, ou seja, é linear O (n) onde n é o comprimento da lista. Corrigido seria O (1)
Mark Robson
0

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

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)

Portanto, há um ciclo formado.

Adit Ya
fonte
Sua solução não passa no requisito "quantidade constante de espaço".
Arnaud #
0

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:

public class LinkedNode{
    public LinkedNode next;
}

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ó:

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

Aqui está um caso de teste simples de três nós que o último nó apontando para o segundo nó:

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));
    }
}
Habib Karbasian
fonte
0

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:

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
}
Sarthak Mehra
fonte
Tem certeza de que isso produz o resultado certo em todas as situações? Se você executar esse algoritmo na lista 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 3 -> ..., acredito que retornaria 4 como o cabeçalho, enquanto você queria 3.
Sunreef 27/06
A questão é apenas descobrir se existe um loop ou não.Neste caso, sim, a questão funcionará perfeitamente e obterá o resultado booleano desejado para o caso.Se você deseja o nó exato de onde o loop foi iniciado, então iremos precisa adicionar algo mais ao código. Mas, no que diz respeito a produzir um resultado, isso produzirá uma conclusão mais rápida.
Sarthak Mehra
Você não leu a pergunta corretamente: Qual é a melhor maneira de escrever 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?
28416 Sunreef
O primeiro valor significa ponteiro de volta e a segunda parte significa ponteiro de avanço. (1,1) - (1,3) - (2,3) - (2,5) - (3,5) - (3,7) - (4,7) - (4,4).
Sarthak Mehra
Na verdade, percebo agora que existem duas maneiras de entender a questão (ou pelo menos vejo duas interpretações diferentes). Seu algoritmo está correto se você estiver apenas pesquisando se houver um loop. Mas eu pensei que a pergunta estava perguntando onde o loop estava começando.
Sunreef
0

Aqui está a minha solução em 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;
}
Irshad ck
fonte
0

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)

xnorcode
fonte
0

Aqui está a solução para detectar o 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 
        }
vishwaraj
fonte
0

// função de loop de localização 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;
}
Sonu Mishra
fonte
-1

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.

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;  
  }  
rai.skumar
fonte
Isso não atende à quantidade constante de restrição de espaço fornecida na pergunta!
dedek
concorda que tem espaço em cima; é outra abordagem para resolver esse problema. A abordagem óbvia é o algoritmo de tartaruga e harse.
rai.skumar
@ downvoter, seria útil se você pudesse explicar o motivo também.
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;

}
edst
fonte