Detectando o início de um Loop em uma lista encadeada (em Java)

Eventualmente, é bom revisitar alguns temas clássicos de ciências da computação. Nesse post, vamos ver como detectar o nodo de início de um Loop em uma lista encadeada.

Loop em uma Lista Encadeada

Dessa vez, estaremos usando Java. Entretanto, o código em C# seria bastante parecido.

O que é uma lista encadeada?

Pegando a definição da wikipedia:

Uma lista ligada ou lista encadeada é uma estrutura de dados linear e dinâmica. Ela é composta por células que apontam para o próximo elemento da lista. Para “ter” uma lista ligada/encadeada, basta guardar seu primeiro elemento, e seu último elemento aponta para uma célula nula.

Definindo um Nodo

O primeiro passo para definir uma Lista Encadeada é definir um Nodo. Eis uma implementação simples:

package com.elemarjr.LinkedListsXp;

public class Node<T> {
	
	private T data;
	private Node<T> next;
	
	public Node() {}
	
	public Node(T data)
	{
		this();
		this.data = data;
	}
	
	public T getData() { return data; }
	public void setData(T value) { data = value; }
	
	public Node<T> getNext() { return next; }
	public void setNext(Node<T> value) { next = value; }
}

Aqui, nada de especial. Temos data armazenando o valor correspondente ao nodo e next apontando para o próximo nodo da lista.

Alguns testes:

package com.elemarjr.LinkedListsXp.Tests;

import com.elemarjr.LinkedListsXp.*;
import static org.junit.Assert.*;

import org.junit.Test;

public class NodeTests {

	@Test
	public void nodeStoresDataPassedWithCtor() {
		Node<String> node = new Node<>("abc");
		assertEquals(node.getData(), "abc");
	}
	
	@Test
	public void nodeStoresNext() {
		Node<String> node1 = new Node<>("abc");
		Node<String> node2 = new Node<>("def");
		
		node1.setNext(node2);
		assertEquals(node1.getNext(), node2);
	}
	
	@Test
	public void nodeStoresData() {
		Node<String> node = new Node<>();
		node.setData("abc");
		assertEquals(node.getData(), "abc");
	}
}

Implementando a lista

A lista consiste basicamente de uma referência para o primeiro nodo da lista. Interessante, mesmo é a implementação do método que verifica se a lista é circular e do outro que encontra o primeiro elemento.

package com.elemarjr.LinkedListsXp;

import java.util.List;

public class LinkedList<T> {
	
	Node<T> head;
	
	public LinkedList(Node<T> head) {
		this.head = head;
	}
	
	public LinkedList(List<T> elements) {
		Node<T> previous = null;
		for (T element: elements) {
			Node<T> node = new Node<>(element);
			if (previous != null) {
				previous.setNext(node);
			} else {
				head = node;
			}
			previous = node;
		}
	}
	
	public Node<T> getHead() { 
		return head;
	}
	
	public void setHead(Node<T> value) {
		head = value;
	}
	
	public boolean isCircular() {
		Node<T> slow = head;
		Node<T> fast = head;
		while (fast != null && fast.getNext() != null) {
			slow = slow.getNext();
			fast = fast.getNext().getNext();
			if (slow == fast) return true;
		}
		return false;
	}
	
	public Node<T> firstNodeOfCircularLoop() {
		Node<T> slow = head, fast = head;
		
		while (fast != null && fast.getNext() != null) {
			slow = slow.getNext();
			fast = fast.getNext().getNext();
			if (slow == fast) break;
		}
		
		if (fast == null || fast.getNext() == null) 
		{
			return null;
		}
		
		slow = head;
		while (slow != fast)
		{
			slow = slow.getNext();
			fast = fast.getNext();
		}
		
		return slow;
	}
}

Defini dois construtores Um que aceita uma referência para o Nodo “head” e outro que aceita uma lista que será convertida para lista ligada.

O método que verifica se a lista é circular inicia dois cursores. Um que vale a lista avançando um elemento por vez, e outro que avança dois elementos. Se, em algum momento, ambos os cursores apontarem para o mesmo nodo, então, teremos uma lista circular. Perceba que não há chances do cursor rápido simplesmente pular o cursor lento.

Eis os testes:

package com.elemarjr.LinkedListsXp.Tests;

import com.elemarjr.LinkedListsXp.*;

import java.util.Arrays;

import org.junit.Test;
import static org.junit.Assert.*;

public class LinkedListTests {

	@Test
	public void createLinkedListFromList() {
		Integer[] values = new Integer[] {1, 2, 3, 4, 5};
		LinkedList<Integer> list = new LinkedList<>(Arrays.asList(values));
		
		Node<Integer> head = list.getHead();
		int index = 0;
		
		while (head != null) {
			assertEquals(head.getData(),values[index]);
			index++;
			head = head.getNext();
		}
	}
	
	@Test
	public void isCircularReturnsFalseWhenListIsNotCircular() {
		Integer[] values = new Integer[] {1, 2, 3, 4, 5};
		LinkedList<Integer> list = new LinkedList<>(Arrays.asList(values));
		assertFalse(list.isCircular());
	}
	
	@Test
	public void isCircularReturnsTrueWhenListIsCircular() {
		Node<Integer> one = new Node<>(1);
		Node<Integer> two = new Node<>(2);
		one.setNext(two);
		Node<Integer> three = new Node<>(3);
		two.setNext(three);
		Node<Integer> four = new Node<>(4);
		three.setNext(four);
		Node<Integer> five = new Node<>(5);
		four.setNext(five);
		five.setNext(three);
		
		LinkedList<Integer> list = new LinkedList<>(one);
		assertTrue(list.isCircular());
	}
	
	@Test
	public void firstNodeOfCircularLoopReturnsCorrectNode() {
		Node<Integer> one = new Node<>(1);
		Node<Integer> two = new Node<>(2);
		one.setNext(two);
		Node<Integer> three = new Node<>(3);
		two.setNext(three);
		Node<Integer> four = new Node<>(4);
		three.setNext(four);
		Node<Integer> five = new Node<>(5);
		four.setNext(five);
		five.setNext(three);
		
		LinkedList<Integer> list = new LinkedList<>(one);
		
		assertEquals(three, list.firstNodeOfCircularLoop());
	}
	
	@Test
	public void firstNodeOfCircularLoopReturnsNullWhenListIsNotCircular() {
		Integer[] values = new Integer[] {1, 2, 3, 4, 5};
		LinkedList<Integer> list = new LinkedList<>(Arrays.asList(values));
		assertEquals(null, list.firstNodeOfCircularLoop());
	}
}

Pronto!

Compartilhe este insight:

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mais insights para o seu negócio

Veja mais alguns estudos e reflexões que podem gerar alguns insights para o seu negócio:

Frequentemente precisamos fazer referência para outros documentos e isso é natural. Entretanto, há cenários onde o documento que queremos referenciar...
In this post, I will share how to write an ASP.NET Core Identity Storage Provider from the Scratch using RavenDB....
Software em funcionamento é mais relevante que documentação abrangente. Concordo com esse princípio expresso no manifesto ágil. Entretanto, acho que...
Já sabemos como explicitar as relações de um sistema com os demais (diagrama de contexto). Também já sabemos como explicitar...
Neste post, vou compartilhar como dar os primeiros passos com OpenCV, rapidamente, usando Visual Studio 2017 e VcPkg. O que...
Quando pensamos sobre o código-fonte do Roslyn, deveríamos pensar em performance! Eu gostaria de compartilhar algumas técnicas de performance e...

Inscrição realizada com sucesso!

No dia da masterclass você receberá um e-mail com um link para acompanhar a aula ao vivo. Até lá!

A sua subscrição foi enviada com sucesso!

Aguarde, em breve entraremos em contato com você para lhe fornecer mais informações sobre como participar da mentoria.

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano anual do Clube de Estudos:

Crie sua conta

Preencha os dados para iniciar o seu cadastro no plano mensal do Clube de Estudos:

× Precisa de ajuda?