Il nodo finale di un elenco collegato che ha un ciclo si riferirà a un altro nodo nello stesso elenco piuttosto che NULL.Se c'è un nodo in un elenco collegato a cui è possibile accedere ripetutamente seguendo il puntatore successivo, si dice che l'elenco abbia un ciclo.
In genere, l'ultimo nodo dell'elenco collegato si riferisce a un riferimento nullo per indicare la conclusione dell'elenco. Tuttavia, in un elenco collegato con un ciclo, il nodo finale dell'elenco si riferisce a un nodo iniziale, un nodo interno o stesso. Pertanto, in tali circostanze, dobbiamo identificare e terminare il ciclo impostando il riferimento del nodo successivo a NULL. La parte di rilevamento del ciclo in un elenco collegato è spiegata in questo articolo.
In C ++, ci sono numerosi modi per trovare loop in un elenco collegato:
Approccio basato sulla tabella hash: Questo approccio memorizza gli indirizzi dei nodi visitati in una tabella hash. Esiste un ciclo nell'elenco collegato se un nodo è già presente nella tabella hash quando viene visitato di nuovo.
Approccio al ciclo di Floyd: L'algoritmo "Tartotae e lepre", comunemente noto come l'algoritmo di ricerca del ciclo di Floyd: questa tecnica utilizza due puntatori, uno che si muove più lentamente dell'altro e l'altro che si muove più rapidamente. Il puntatore più rapido alla fine supererà il puntatore più lento se c'è un loop nell'elenco collegato, rivelando l'esistenza del loop.
Metodo ricorsivo: Questo metodo passa attraverso l'elenco collegato chiamando più e più volte. L'elenco collegato contiene un ciclo se il nodo corrente è stato precedentemente visitato.
Approccio basato su stack: Questo approccio memorizza gli indirizzi dei nodi visitati in uno stack. Un ciclo nell'elenco collegato è presente se un nodo è già lì nello stack quando viene visitato di nuovo.
Spieghiamo ogni approccio in dettaglio per comprendere il concetto.
Approccio 1: approccio hashset
L'utilizzo di hashing è il metodo più semplice. Qui, passiamo attraverso l'elenco uno per uno mantenendo una tabella hash con gli indirizzi del nodo. Pertanto, esiste un ciclo se mai attraversiamo il prossimo indirizzo dell'attuale nodo che è già contenuto nella tabella hash. Altrimenti, non esiste un loop nell'elenco collegato se ci incontriamo in null (i.e., raggiungere la fine dell'elenco collegato).
Sarà abbastanza facile implementare questa strategia.
Mentre attraverseremo l'elenco collegato, utilizzeremo un non ordinato_hashmap e continueremo ad aggiungere nodi ad esso.
Ora, una volta imbattuto in un nodo che è già mostrato nella mappa, sapremo che siamo arrivati all'inizio del loop.
In questo modo, il nodo Loop finale invece di fare riferimento al nodo iniziale del loop, inizia a indicare Null.
La complessità medio del tempo e dello spazio del metodo di hashing è O (N). Il lettore dovrebbe sempre essere consapevole del fatto che l'implementazione della dati di hashing integrata nel linguaggio di programmazione determinerà la complessità del tempo totale in caso di collisione in hashing.
Implementazione del programma C ++ per il metodo sopra (hashset):
#includere
Utilizzo dello spazio dei nomi std;
nodo struct
valore int;
nodo struct* Next;
;
Nodo* newnode (valore int)
Nodo* tempNode = nuovo nodo;
tempNode-> value = value;
tempnode-> next = null;
restituire tempnode;
// Identifica ed elimina eventuali loop potenziali
// in un elenco collegato con questa funzione.
void functionhashmap (nodo* headnode)
// ha creato un non ordinato_map per implementare la mappa hash
UNORDERD_MAPhash_map;
// puntatore a LastNode
Nodo* lastnode = null;
mentre (headnode != Null)
// Se manca un nodo dalla mappa, aggiungilo.
if (hash_map.Trova (headnode) == hash_map.FINE())
hash_map [headnode] ++;
LastNode = headNode;
headNode = headNode-> Next;
// Se è presente un ciclo, imposta il prossimo puntatore del nodo finale su NULL.
altro
lastnode-> next = null;
rottura;
// Visualizza l'elenco collegato
void display (nodo* headnode)
mentre (headnode != Null)
coutheadNode = headNode-> Next;
cout << endl;
/* Funzione principale*/
int main ()
Nodo* 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);
/ * Crea un ciclo in Linkedlist */
HeadNode-> Next-> Next-> Next-> Next-> Next = HeadNode-> Next-> Next;
functionhashmap (headnode);
printf ("Elenco collegato senza loop \ n");
display (headnode);
restituzione 0;
Produzione:
Elenco collegato senza loop
48 18 13 2 8
La spiegazione passo-passo del codice è fornita di seguito:
Approccio 2: il ciclo di Floyd
L'algoritmo di rilevamento del ciclo di Floyd, spesso noto come algoritmo di tartaruga e lepre, fornisce le basi per questo metodo di localizzazione dei cicli in un elenco collegato. Il puntatore "lento" e il puntatore "veloce", che attraversano l'elenco a varie velocità, sono i due puntatori usati in questa tecnica. Il puntatore rapido fa avanzare due passaggi mentre il puntatore lento fa avanzare un passo ogni iterazione. Un ciclo nell'elenco collegato esiste se i due punti si trovano mai faccia a faccia.
1. Con il nodo principale dell'elenco collegato, inizializzamo due puntatori chiamati Fast and Slow.
2. Ora eseguiamo un ciclo per spostarci attraverso l'elenco collegato. Il puntatore veloce deve essere spostato due posizioni davanti al puntatore lento al passo di ogni iterazione.
3. Non ci sarà un ciclo nell'elenco collegato se il puntatore veloce raggiunge la fine dell'elenco (FastPointer == Null o FastPointer-> Next == Null). In caso contrario, i puntatori veloci e lenti alla fine si incontreranno, il che significa che l'elenco collegato ha un ciclo.
Implementazione del programma C ++ per il metodo sopra (Floyd's Cycle):
#includere
Utilizzo dello spazio dei nomi std;
/ * Nodo dell'elenco dei collegamenti */
nodo struct
Int Data;
nodo struct* Next;
;
/* Funzione per rimuovere il ciclo. */
void deleteLoop (nodo struct*, nodo struct*);
/* Questa funzione individua ed elimina i loop di elenco. Produce 1
Se c'era un ciclo nell'elenco; altro, restituisce 0. */
Int DetectandDeleTeloop (elenco di nodo struct*)
nodo struct *slowptr = elenco, *FastPtr = elenco;
// iterazione per verificare se il ciclo è lì.
while (slowptr && FastPtr && FastPtr-> Next)
slowptr = slowptr-> next;
FastPtr = FastPtr-> Next-> Next;
/* Se SlowPtr e FastPtr si incontrano ad un certo punto, allora lì
è un ciclo */
if (slowptr == FastPtr)
deleteLoop (slowptr, elenco);
/* Restituisce 1 per indicare che è stato scoperto un ciclo. */
Ritorno 1;
/* Restituisce 0 per indicare che non è stato scoperto alcun ciclo.*/
restituzione 0;
/* Funzione per eliminare loop dall'elenco collegato.
Loopnode punta a uno dei nodi del loop e il dono è puntato
al nodo iniziale dell'elenco collegato */
void deleteloop (nodo struct* loopnode, nodo struct* headnode)
nodo struct* ptr1 = loopnode;
nodo struct* ptr2 = loopnode;
// Conta quanti nodi ci sono nel ciclo.
Unsigned int k = 1, i;
while (PTR1-> Next != ptr2)
PTR1 = ptr1-> Next;
K ++;
// fissa un puntatore a headnode
ptr1 = headnode;
// e l'altro puntatore ai nodi K dopo il dono
ptr2 = headnode;
per (i = 0; i < k; i++)
ptr2 = ptr2-> next;
/* Quando entrambi i punti vengono spostati contemporaneamente,
Si scontreranno nel nodo iniziale del loop. */
mentre (PTR2 != ptr1)
PTR1 = ptr1-> Next;
ptr2 = ptr2-> next;
// ottieni l'ultimo puntatore del nodo.
while (PTR2-> Next != PTR1)
ptr2 = ptr2-> next;
/* Per chiudere il ciclo, impostare il successivo
nodo al nodo terminale del loop. */
ptr2-> next = null;
/ * Funzione per visualizzare l'elenco collegato */
void DisplayLinkedList (nodo struct* nodo)
// Visualizza l'elenco collegato dopo eliminare il loop
mentre (nodo != Null)
cout node = nodo-> next;
Struct Node* newnode (tasto int)
Struct node* temp = new node ();
temp-> data = chiave;
temp-> next = null;
restituire temp;
// Codice principale
int main ()
Struct Node* 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);
/ * Crea un loop */
HeadNode-> Next-> Next-> Next-> Next-> Next = HeadNode-> Next-> Next;
// Visualizza il ciclo nell'elenco collegato
// DisplayLinkEdList (HeadNode);
DetectandDeleTeloop (headnode);
cout << "Linked List after no loop \n";
DisplayLinkEdList (headnode);
restituzione 0;
Produzione:
Elenco collegato dopo nessun ciclo
48 18 13 2 8
Spiegazione:
Approccio 3: ricorsione
La ricorsione è una tecnica per risolvere i problemi partizionandoli in sottoproblemi più piccoli e più facili. La ricorsione può essere utilizzata per attraversare un elenco singolarmente collegato nel caso in cui un ciclo venga trovato eseguendo continuamente una funzione per il nodo successivo nell'elenco fino al raggiungimento della fine dell'elenco.
In un elenco singolarmente collegato, il principio di base dietro l'uso della ricorsione per trovare un ciclo è iniziare a testa dell'elenco, contrassegnare il nodo corrente come visitato in ogni fase, quindi passare al nodo successivo invocando ricorsivamente la funzione per quel nodo. Il metodo iterirà nell'elenco collegato completo perché si chiama ricorsivamente.
L'elenco contiene un ciclo se un nodo che è stato precedentemente contrassegnato come visitato è riscontrato dalla funzione; In questo caso, la funzione può restituire vero. Il metodo può restituire falso se raggiunge la fine dell'elenco senza attraversare un nodo visitato, il che indica che non esiste un ciclo.
Sebbene questa tecnica per l'utilizzo della ricorsione per trovare un ciclo in un unico elenco collegato sia semplice da usare e comprendere, potrebbe non essere la più efficace in termini di complessità del tempo e dello spazio.
Implementazione del programma C ++ per il metodo sopra (ricorsione):
#includere
Utilizzo dello spazio dei nomi std;
nodo struct
Int Data;
Nodo* Next;
Bool ha visitato;
;
// funzione di rilevamento del loop elenco collegato
Bool DetectLoopLinkedList (nodo* headnode)
if (headnode == null)
restituire false; // Se l'elenco collegato è vuoto, il caso di base
// c'è un ciclo se il nodo corrente ha
// già è stato visitato.
if (headnode-> visitato)
restituire vero;
// Aggiungi un segno di visita al nodo corrente.
headnode-> visitato = true;
// Chiama ripetutamente il codice per il nodo successivo
restituire DETTACKLINKEDLIST (headnode-> next);
int main ()
Nodo* headNode = new node ();
Nodo* secondNode = new node ();
Nodo* terzoNode = new node ();
headnode-> data = 1;
HeadNode-> Next = SecondNode;
headnode-> visitato = false;
SecondNode-> data = 2;
SecondNode-> Next = ThirdNode;
SecondNode-> visitato = false;
ThirdNode-> data = 3;
ThirdNode-> Next = null; // Nessun loop
ThirdNode-> visitato = false;
if (DetectLoopLinkedList (headnode))
cout << "Loop detected in linked list" << endl;
altro
cout << "No loop detected in linked list" << endl;
// Creazione di un ciclo
ThirdNode-> Next = SecondNode;
if (DetectLoopLinkedList (headnode))
cout << "Loop detected in linked list" << endl;
altro
cout << "No loop detected in linked list" << endl;
restituzione 0;
Produzione:
Nessun ciclo rilevato nell'elenco collegato
Loop rilevato nell'elenco collegato
Spiegazione:
Approccio 4: usando lo stack
I loop in un elenco collegato sono disponibili utilizzando uno stack e il metodo "Profondità-prima di ricerca" (DFS). Il concetto fondamentale è quello di iterare attraverso l'elenco collegato, spingendo ogni nodo sullo stack se non è già stato visitato. Un ciclo viene riconosciuto se si incontra nuovamente un nodo che è già sullo stack.
Ecco una breve descrizione della procedura:
Implementazione del programma C ++ per il metodo sopra (Stack)
#includere
#includere
Utilizzo dello spazio dei nomi std;
nodo struct
Int Data;
Nodo* Next;
;
// funzione per rilevare loop in un elenco collegato
Bool DetectLoopLinkedList (nodo* headnode)
pilapila;
Nodo* tempNode = headnode;
mentre (tempnode != Null)
// Se l'elemento superiore dello stack corrisponde
// Il nodo corrente e lo stack non è vuoto
Se (!pila.vuoto () && stack.top () == tempNode)
restituire vero;
pila.push (tempnode);
tempNode = tempNode-> Next;
restituire false;
int main ()
Nodo* headNode = new node ();
Nodo* secondNode = new node ();
Nodo* terzoNode = new node ();
headnode-> data = 1;
HeadNode-> Next = SecondNode;
SecondNode-> data = 2;
SecondNode-> Next = ThirdNode;
ThirdNode-> data = 3;
ThirdNode-> Next = null; // Nessun loop
if (DetectLoopLinkedList (headnode))
cout << "Loop detected in linked list" << endl;
altro
cout << "No loop detected in linked list" << endl;
// Creazione di un ciclo
ThirdNode-> Next = SecondNode;
if (DetectLoopLinkedList (headnode))
cout << "Loop detected in linked list" << endl;
altro
cout << "No loop detected in linked list" << endl;
Produzione:
Nessun ciclo rilevato nell'elenco collegato
Loop rilevato nell'elenco collegato
Spiegazione:
Questo programma utilizza uno stack per scoprire se un elenco singolarmente collegato ha un ciclo.
2. Lo spazio dei nomi standard è incluso nella seconda riga, permettendoci di accedere alle funzioni della libreria di flusso di input/output senza doverle prefigurare con "std ::."
3. La riga seguente definisce il nodo struct, che consiste in due membri: un numero intero chiamato "dati" e un puntatore a un altro nodo chiamato "Avanti."
4. La testa dell'elenco collegato è un input per il metodo DetectLoopLinkedList (), che è definito nella riga successiva. La funzione produce un valore booleano che indica se è stato trovato o meno un ciclo.
5. Uno stack di puntatori del nodo chiamato "stack" e un puntatore a un nodo chiamato "tempnode" che viene inizializzato con il valore del headnode sono entrambi creati all'interno della funzione.
6. Quindi, fintanto che il tempnode non è un puntatore nullo, entriamo un ciclo di tempo.
(a) L'elemento superiore dello stack deve corrispondere al nodo corrente affinché noi non sia vuoto. Ritorniamo vero se questo è il caso perché è stato trovato un ciclo.
(b) Se la condizione di cui sopra è falsa, il puntatore del nodo corrente viene spinto sullo stack e tempnode è impostato sul nodo seguente nell'elenco collegato.
7. Ritorniamo falsi dopo il ciclo while perché non è stato osservato alcun ciclo.
8. Costruiamo tre oggetti nodi e li iniziali nella funzione principale (). Dal momento che non esiste un loop nel primo esempio, impostiamo correttamente i puntatori "successivi" di ciascun nodo.
Conclusione:
In conclusione, il metodo migliore per rilevare i loop in un elenco collegato dipende dal caso d'uso specifico e dai vincoli del problema. La tabella hash e l'algoritmo di ricerca del ciclo di Floyd sono metodi efficienti e sono ampiamente utilizzati nella pratica. Lo stack e la ricorsione sono metodi meno efficienti ma sono più intuitivi.