Detecte um loop em uma lista vinculada em C ++

Detecte um loop em uma lista vinculada em C ++

O nó final de uma lista vinculada que possui um loop se referirá a outro nó na mesma lista, em vez do nulo.Se houver um nó em uma lista vinculada que possa ser acessada repetidamente seguindo o próximo ponteiro, diz -se que a lista tem um ciclo.

Normalmente, o último nó da lista vinculada refere -se a uma referência nula para denotar a conclusão da lista. No entanto, em uma lista vinculada com um loop, o nó final da lista refere -se a um nó inicial, um nó interno ou em si mesmo. Portanto, em tais circunstâncias, devemos identificar e encerrar o loop, definindo a referência do próximo nó a nulo. A parte de detecção do loop em uma lista vinculada é explicada neste artigo.




Em C ++, existem inúmeras maneiras de encontrar loops em uma lista vinculada:

Abordagem baseada na mesa de hash: Esta abordagem armazena os endereços dos nós visitados em uma tabela de hash. Existe um loop na lista vinculada se um nó já estiver presente na tabela de hash quando for visitado novamente.

Abordagem de ciclo de Floyd: O algoritmo “Tartaruga e lebre”, comumente conhecido como algoritmo de encontrar ciclo de Floyd: essa técnica faz uso de dois ponteiros, um se movendo mais lentamente do que o outro e o outro se movendo mais rapidamente. O ponteiro mais rápido acabará por ultrapassar o ponteiro mais lento se houver um loop na lista vinculada, revelando a existência do loop.

Método recursivo: Este método passa pela lista vinculada ligando -se repetidamente. A lista vinculada contém um loop se o nó atual foi visitado anteriormente.

Abordagem baseada em pilha: Esta abordagem armazena os endereços dos nós visitados em uma pilha. Um loop na lista vinculado está presente se um nó já estiver lá na pilha quando for visitado novamente.

Vamos explicar cada abordagem em detalhes para entender o conceito.

Abordagem 1: abordagem de hashset

Utilizar o hash é o método mais direto. Aqui, passamos pela lista um por um enquanto mantemos uma tabela de hash com os endereços do nó. Portanto, existe um loop se algum dia encontrarmos o próximo endereço do nó atual que já está contido na tabela de hash. Caso contrário, não há loop na lista vinculada se encontrarmos null (i.e., alcançar o final da lista vinculada).

Será muito fácil implementar esta estratégia.

Ao atravessar a lista vinculada, utilizaremos um UNODEDED_HASHMAP e continuamos adicionando nós a ela.

Agora, uma vez que encontramos um nó que já é mostrado no mapa, saberemos que chegamos ao começo do loop.

    • Além disso, mantivemos dois ponteiros em cada etapa, Cabeça apontando para o nó atual e LastNode apontando para o nó anterior do nó atual, enquanto itera.
    • Como nosso Cabeça agora está apontando para o nó de início do loop e como LastNode estava apontando para o nó para o qual a cabeça estava apontando (eu.e., está se referindo ao LastNode do loop), nosso Cabeça está atualmente apontando para o nó de início do loop.
    • O loop será quebrado definindo lAstnode-> Next == NULL.

Ao fazer isso, o nó do laço final, em vez de se referir ao nó inicial do loop, começa a apontar para NULL.

O tempo médio e a complexidade do espaço do método de hash é O (n). O leitor deve sempre estar ciente de que a implementação daestrutura de dados de hash interno na linguagem de programação determinará a complexidade total do tempo no caso de uma colisão no hashing.

Implementação do programa C ++ para o método acima (hashset):

#incluir
usando namespace std;
Nó da estrutura
int valor;
Nó da estrutura* Em seguida;
;
Nó* newNode (int valor)

Nó* tempnode = novo nó;
tempnode-> value = value;
tempnode-> próximo = null;
retornar tempnode;

// Identifique e elimine qualquer potencial loops
// em uma lista vinculada com esta função.
Void functionHashmap (nó* HeadNode)

// criou um não -ordered_map para implementar o mapa de hash
UNODERED_MAP hash_map;
// Ponteiro para Lugarnode
Nó* lastNode = null;
while (HeadNode != Null)
// Se falta um nó no mapa, adicione -o.
if (hash_map.encontre (headNode) == hash_map.fim())
hash_map [headNode] ++;
lastNode = headNode;
HeadNode = HeadNode-> Next;

// Se um ciclo estiver presente, defina o próximo ponteiro do nó final como nulo.
outro
lastNode-> próximo = null;
quebrar;



// Lista vinculada de exibição
Void Display (nó* HadNode)

while (HeadNode != Null)
cout HeadNode = HeadNode-> Next;

cout << endl;

/* Função principal*/
int main ()

Nó* headNode = newNode (48);
HeadNode-> Next = HeadNode;
HeadNode-> Next = newNode (18);
HeadNode-> Next-> Next = newNode (13);
HeadNode-> Next-> Next-> Next = newNode (2);
HeadNode-> Next-> Next-> Next-> Next = newNode (8);
/ * Crie um loop no LinkedList */
HeadNode-> Next-> Next-> Next-> Next-> Next = headNode-> Next-> Next;
functionHashMap (HeadNode);
printf ("Lista vinculada sem loop \ n");
tela (headNode);
retornar 0;

Saída:

Lista vinculada sem loop
48 18 13 2 8

A explicação passo a passo do código é fornecida abaixo:

    1. Os bits/stdc++.H> arquivo de cabeçalho, que contém todas as bibliotecas C ++ comum, está incluído no código.
    2. Uma estrutura chamada "nó" é construída e possui dois membros: uma referência ao próximo nó na lista e um número inteiro chamado “valor.”
    3. Com um valor inteiro como entrada e o ponteiro "próximo" definido como nulo, a função "newNode" cria um novo nó com esse valor.
    4. A função 'functionhashmap ' é definido, que leva um ponteiro para o nó da cabeça da lista vinculada como entrada.
    5. Dentro de 'functionHashmap'Função, um UNODERED_MAP nomeado' Hash_map 'é criado, que é usado para implementar uma estrutura de dados de mapa de hash.
    6. Um ponteiro para o último nó da lista é inicializado para nulo.
    7. Um loop de tempo é usado para atravessar a lista vinculada, que começa no nó da cabeça e continua até que o nó da cabeça seja nulo.
    8. O ponteiro LastNode é atualizado para o nó atual dentro do while loop se o nó atual (HeadNode) ainda não estiver presente no mapa de hash.
    9. Se o nó atual for encontrado no mapa, significa que um loop está presente na lista vinculada. Para remover o loop, o próximo ponteiro do LastNode está configurado para NULO E o loop do tempo está quebrado.
    10. O nó da cabeça da lista vinculada é usada como entrada para uma função chamada "exibição", que gera o valor de cada nó na lista do começo ao fim.
    11. No principal função, criando um loop.
    12. A função 'FunctionHashmap' é chamada com o ponteiro da cabeça como entrada, que remove o loop da lista.
    13. A lista modificada é exibida usando a função 'exibição'.

Abordagem 2: Ciclo de Floyd

O algoritmo de detecção de ciclo de Floyd, muitas vezes conhecido como Tartaruga e Algoritmo Hare, fornece a base para esse método de localizar ciclos em uma lista vinculada. O ponteiro "lento" e o ponteiro "rápido", que atravessam a lista em várias velocidades, são os dois ponteiros usados ​​nesta técnica. O ponteiro rápido avança duas etapas, enquanto o ponteiro lento avança um passo a cada iteração. Existe um ciclo na lista vinculada se os dois pontos ficaram cara a cara.

1. Com o nó da cabeça da lista vinculada, inicializamos dois ponteiros chamados.

2. Agora corremos um loop para passar pela lista vinculada. O ponteiro rápido deve ser movido dois locais em frente ao ponteiro lento na etapa de cada iteração.

3. Não haverá um loop na lista vinculada se o ponteiro rápido chegar ao final da lista (FastPointer == NULL ou FASTPOINTER-> NEXT == NULL). Caso contrário, os ponteiros rápidos e lentos acabarão se encontrando, o que significa que a lista vinculada tem um loop.

Implementação do programa C ++ para o método acima (ciclo de Floyd):

#incluir
usando namespace std;
/ * Link list node */
Nó da estrutura
int dados;
Nó da estrutura* Em seguida;
;
/* Função para remover o loop. */
vazio deleteeloop (nó da estrutura*, nó da estrutura*);
/* Esta função localiza e elimina loops de lista. Ele produz 1
se houvesse um loop na lista; caso contrário, ele retorna 0. */
int DetectAndDeleteLoop (Lista de Nó da estrutura*)

Nó da estrutura *lentaptr = list, *fastptr = list;
// itera para verificar se o loop está lá.
while (SlowPtr && FastPtr && FastPtr-> Next)
SlowPtr = SlowPtr-> Em seguida;
fastptr = fastptr-> próximo-> próximo;
/* Se SlowPtr e FastPtr se encontrarem em algum momento, então lá
é um loop */
if (SlowPtr == FastPtr)
deleteeloop (lenta, lista);
/* Retorne 1 para indicar que um loop foi descoberto. */
retornar 1;


/* Retorne 0 para indicar que nenhum loop foi descoberto.*/
retornar 0;

/* Função para excluir loop da lista vinculada.
O loopnode está apontando para um dos nós de loop e a cabeça está apontando
para o nó inicial da lista vinculada */
Void Deleteloop (nó da estrutura* Loopnode, Nó da estrutura* HadNode)

Nó da estrutura* ptr1 = loopnode;
Nó da estrutura* ptr2 = loopnode;
// Conte quantos nós estão no loop.
não assinado int k = 1, i;
while (ptr1-> próximo != ptr2)
ptr1 = ptr1-> próximo;
k ++;

// conserte um ponteiro para o cabeçote
pTr1 = HeadNode;
// e o outro ponteiro para k nós após o código do cabeçote
pTr2 = HeadNode;
para (i = 0; i < k; i++)
ptr2 = ptr2-> próximo;
/* Quando os dois pontos são movidos simultaneamente,
Eles vão colidir no nó inicial do loop. */
while (ptr2 != ptr1)
ptr1 = ptr1-> próximo;
ptr2 = ptr2-> próximo;

// Obtenha o último ponteiro do nó.
while (ptr2-> próximo != ptr1)
ptr2 = ptr2-> próximo;
/* Para fechar o loop, defina o subsequente
nó para o nó terminante do loop. */
ptr2-> próximo = nulo;

/ * Função para exibir lista vinculada */
Void DisplayLinkedList (nó da estrutura* nó)

// exibe a lista vinculada após excluir o loop
while (nó != Null)
cout nó = nó-> a seguir;


Nó da estrutura* newNode (tecla int)

Nó da estrutura* temp = new Node ();
temp-> data = key;
temp-> próximo = nulo;
retornar temp;

// Código principal
int main ()

Nó da estrutura* HeadNode = newNode (48);
HeadNode-> Next = newNode (18);
HeadNode-> Next-> Next = newNode (13);
HeadNode-> Next-> Next-> Next = newNode (2);
HeadNode-> Next-> Next-> Next-> Next = newNode (8);
/ * Crie um loop */
HeadNode-> Next-> Next-> Next-> Next-> Next = headNode-> Next-> Next;
// exibe o loop na lista vinculada
// DisplayLinkedList (HeadNode);
DetectandDeleteLoop (HeadNode);
cout << "Linked List after no loop \n";
DisplayLinkedList (HeadNode);
retornar 0;

Saída:

Lista vinculada após nenhum loop
48 18 13 2 8

Explicação:

    1. Os cabeçalhos relevantes, como “bits/stdc++.h "e" std :: cout ", estão incluídos pela primeira vez.
    2. A estrutura do "nó", que significa um nó na lista vinculada, é então declarada. Um próximo ponteiro que leva ao seguinte nó na lista é incluído junto com um campo de dados inteiro em cada nó.
    3. Em seguida, define "Deleteloop" e "DetectandDeleteLoop", duas funções. Um loop é removido de uma lista vinculada usando o primeiro método e um loop é detectado na lista usando a segunda função, que então chama o primeiro procedimento para remover o loop.
    4. Uma nova lista vinculada com cinco nós é criada na função principal e um loop é estabelecido definindo o próximo ponteiro do último nó para o terceiro nó.
    5. Em seguida, faz uma chamada para o método "DetectandDeleteLoop" ao passar o nó da cabeça da lista vinculada como um argumento. Para identificar loops, essa função use a abordagem "lenta e rápida". Emprega dois ponteiros que começam no topo da lista, SlowPtr e FastPtr. Enquanto o ponteiro rápido move dois nós de uma só vez, o ponteiro lento apenas move um nó de cada vez. O ponteiro rápido ultrapassará o ponteiro lento se a lista contiver um loop, e os dois pontos colidirão no mesmo nó.
    6. A função chama a função "Deleteloop" se encontrar um loop, fornecendo o nó da cabeça da lista e a interseção dos ponteiros lentos e rápidos como entradas. Este procedimento estabelece dois ponteiros, ptr1 e ptr2, para o nó da cabeça da lista e conta o número de nós no loop. Depois disso, ele avança um ponteiro k nós, onde k é o número total de nós no loop. Então, até que eles se encontrem no início do loop, ele avança ambos os pontos um nó de cada vez. O loop é então quebrado definindo o próximo ponteiro do nó no final do loop para nulo.
    7. Depois de remover o loop, ele exibe a lista vinculada como uma etapa final.

Abordagem 3: Recursão

Recursão é uma técnica para resolver problemas, particionando -os em subproblemas menores e mais fáceis. A recursão pode ser usada para atravessar uma lista individual, caso um loop seja encontrado executando continuamente uma função para o próximo nó na lista até que o final da lista seja alcançado.

Em uma lista individual, o princípio básico por trás do uso da recursão para encontrar um loop é começar à frente da lista, marcar o nó atual, conforme visitado em cada etapa e depois continuar para o próximo nó, invocando recursivamente a função para a função para aquele nó. O método irá atingir a lista completa vinculada porque é chamada recursivamente.

A lista contém um loop se um nó que foi marcado anteriormente como visitado for encontrado pela função; Nesse caso, a função pode retornar verdadeiro. O método pode retornar falso se chegar ao final da lista sem correr em um nó visitado, o que indica que não há loop.

Embora essa técnica para utilizar a recursão para encontrar um loop em uma única lista vinculada seja direta para usar e compreender, pode não ser a mais eficaz em termos de tempo e complexidade do espaço.

Implementação do programa C ++ para o método acima (recursão):

#incluir
usando namespace std;
Nó da estrutura
int dados;
Nó* a seguir;
Bool visitou;
;
// Função de detecção de loop de lista vinculada
Bool DetectLoOlinkedList (nó* HeadNode)
if (headNode == null)
retorna falso; // Se a lista vinculada estiver vazia, o caso básico

// existe um loop se o nó atual tiver
// já foi visitado.
if (headNode-> visitado)
retornar true;

// Adicione uma marca de visita ao nó atual.
headNode-> visitou = true;
// chamando o código para o nó subsequente repetidamente
return detectlooplinkedlist (headnode-> próximo);

int main ()
Nó* headNode = new Node ();
Nó* SecondNode = new Node ();
Nó* terceironode = new Node ();
headNode-> dados = 1;
HeadNode-> Next = SecondNode;
headNode-> visitou = false;
SecondNode-> dados = 2;
SecondNode-> Next = ThirdNode;
SecondNode-> visitado = false;
terceironode-> dados = 3;
terceironode-> próximo = nulo; // sem loop
terceironode-> visitado = false;
if (detectlooplinkedlist (headnode))
cout << "Loop detected in linked list" << endl;
outro
cout << "No loop detected in linked list" << endl;

// Criando um loop
terceironode-> next = SecondNode;
if (detectlooplinkedlist (headnode))
cout << "Loop detected in linked list" << endl;
outro
cout << "No loop detected in linked list" << endl;

retornar 0;

Saída:

Nenhum loop detectado na lista vinculada
Loop detectado na lista vinculada

Explicação:

    1. A função DetectloOlinkedList () Neste programa aceita o chefe da lista vinculada como uma entrada.
    2. A recursão é usada pela função para iterar na lista vinculada. Como o caso básico da recursão, começa determinando se o nó atual é nulo. Nesse caso, o método retorna false, indicando que não existe loop.
    3. O valor da propriedade "visitado" do nó atual é verificado para ver se foi visitado anteriormente. Ele retorna verdadeiro se foi visitado, sugerindo que um loop foi encontrado.
    4. A função marca o nó atual, conforme visitado se já foi visitado alterando sua propriedade "visitada" para True.
    5. O valor da variável visitado é então verificado para ver se o nó atual foi visitado anteriormente. Se já foi usado antes, um loop deve existir e a função retorna verdadeira.
    6. Por fim, a função se chama com o próximo nó na lista passando HeadNode-> Em seguida como um argumento. Recursivamente, Isso é realizado até que um loop seja encontrado ou todos os nós tenham sido visitados. Significa que a função define a variável visitada como true se o nó atual nunca foi visitado antes de se chamar recursivamente para o seguinte nó na lista vinculada.
    7. O código constrói três nós e se junta a eles para produzir uma lista vinculada no função principal. O método DetectloOlinkedList () é então invocado no nó da cabeça da lista. O programa produz “Loop deduzido na lista vinculada" se DetectloOlinkedList () retorna verdadeiro; caso contrário, produz “Nenhum loop detectado na lista vinculada““.
    8. O código então insere um loop na lista vinculada definindo o próximo ponteiro do último nó para o segundo nó. Então, dependendo do resultado da função, ele funciona DetectloOlinkedList () novamente e produz “Loop deduzido na lista vinculada" ou "Nenhum loop detectado na lista vinculada.”

Abordagem 4: Usando a pilha

Loops em uma lista vinculada podem ser encontrados usando uma pilha e o método "Pesquisa de profundidade primeiro" (DFS). O conceito fundamental é iterar através da lista vinculada, empurrando cada nó para a pilha, se ainda não foi visitado. Um loop é reconhecido se um nó que já estiver na pilha for encontrado novamente.

Aqui está uma breve descrição do procedimento:

    1. Crie uma pilha vazia e uma variável para registrar os nós que foram visitados.
    2. Empurre a lista vinculada para a pilha, começando no topo. Observe que a cabeça foi visitada.
    3. Empurre o próximo nó na lista na pilha. Adicione uma marca de visita a este nó.
    4. Ao atravessar a lista, empurre cada novo nó na pilha para indicar que ela foi visitada.
    5. Verifique se um nó que foi visitado anteriormente está no topo da pilha, se for encontrado. Nesse caso, um loop foi encontrado e o loop é identificado pelos nós na pilha.
    6. Retire os nós da pilha e continue atravessando a lista se nenhum loop for encontrado.

Implementação do programa C ++ para o método acima (pilha)

#incluir
#incluir
usando namespace std;
Nó da estrutura
int dados;
Nó* a seguir;
;
// Função para detectar loop em uma lista vinculada
Bool DetectLoOlinkedList (nó* HeadNode)
pilha pilha;
Nó* tempnode = headNode;
enquanto (tempnode != Null)
// se o elemento superior da pilha corresponder
// O nó atual e a pilha não estão vazios
se (!pilha.vazio () && pilha.top () == tempnode)
retornar true;

pilha.push (tempnode);
tempnode = tempnode-> próximo;

retorna falso;

int main ()
Nó* headNode = new Node ();
Nó* SecondNode = new Node ();
Nó* terceironode = new Node ();
headNode-> dados = 1;
HeadNode-> Next = SecondNode;
SecondNode-> dados = 2;
SecondNode-> Next = ThirdNode;
terceironode-> dados = 3;
terceironode-> próximo = nulo; // sem loop
if (detectlooplinkedlist (headnode))
cout << "Loop detected in linked list" << endl;
outro
cout << "No loop detected in linked list" << endl;

// Criando um loop
terceironode-> next = SecondNode;
if (detectlooplinkedlist (headnode))
cout << "Loop detected in linked list" << endl;
outro
cout << "No loop detected in linked list" << endl;

Saída:

Nenhum loop detectado na lista vinculada
Loop detectado na lista vinculada

Explicação:

Este programa usa uma pilha para descobrir se uma lista ligada individual tem um loop.

  • 1. A biblioteca de fluxo de entrada/saída e a biblioteca de pilhas estão presentes na primeira linha.

    2. O espaço para nome padrão está incluído na segunda linha, permitindo -nos acessar as funções da biblioteca de fluxo de entrada/saída sem precisar prefixá -las com “STD ::.”

    3. A linha a seguir define o nó da estrutura, que consiste em dois membros: um número inteiro chamado "dados" e um ponteiro para outro nó chamado "Próximo.”

    4. A cabeça da lista vinculada é uma entrada para o método de detectlooplinkedlist (), que é definido na próxima linha. A função produz um valor booleano que indica se um loop foi ou não encontrado.

    5. Uma pilha de ponteiros de nós chamados "pilha" e um ponteiro para um nó chamado "tempnode" que é inicializado com o valor do nó de cabeça são criados dentro da função.

    6. Então, desde que o tempnode não seja um ponteiro nulo, entramos um pouco.

    (a) O elemento superior da pilha deve corresponder ao nó atual para que determos que não está vazio. Retornamos verdadeiro se esse for o caso porque um loop foi encontrado.

    (b) Se a condição acima mencionada for falsa, o ponteiro do nó atual será empurrado para a pilha e o tempnode é definido para o seguinte nó na lista vinculada.

    7. Voltamos falsos após o loop while, porque nenhum loop foi observado.

    8. Construímos três objetos de nós e os iniciamos na função principal (). Como não há loop no primeiro exemplo, definimos os "próximos" ponteiros de cada nó corretamente.

Conclusão:

Em conclusão, o melhor método para detectar loops em uma lista vinculada depende do caso de uso específico e das restrições do problema. A tabela de hash e o algoritmo de encontrar ciclo de Floyd são métodos eficientes e são amplamente utilizados na prática. Pilha e recursão são métodos menos eficientes, mas são mais intuitivos.