Węzeł końcowy połączonej listy, która ma pętlę, będzie odnosić się do innego węzła na tej samej liście, a nie do null.Jeśli istnieje węzeł na połączonej liście, do którego można wielokrotnie dostępić, śledząc następny wskaźnik, mówi się, że lista ma cykl.
Zazwyczaj ostatni węzeł listy powiązanej odnosi się do zerowego odniesienia do oznaczenia wniosku listy. Jednak na połączonej liście z pętlą węzeł końcowy listy odnosi się do węzła startowego, węzła wewnętrznego lub samego siebie. Dlatego w takich okolicznościach musimy zidentyfikować i zakończyć pętlę, ustawiając odniesienie następnego węzła do NULL. Część wykrywalności pętli na połączonej liście jest wyjaśniona w tym artykule.
W C ++ istnieje wiele sposobów na znalezienie pętli na połączonej liście:
Podejście oparte na tabeli HASH: To podejście przechowuje adresy odwiedzonych węzłów w tabeli skrótów. Pętla na połączonej liście istnieje, jeśli węzeł jest już obecny w tabeli skrótów, gdy zostanie ponownie odwiedzony.
Podejście rowerowe Floyda: Algorytm „żółwia i zająca”, powszechnie znany jako algorytm ustalania cyklu Floyda: ta technika wykorzystuje dwa wskaźniki, jeden poruszający się wolniej niż drugi, a drugi porusza się szybciej. Szybszy wskaźnik ostatecznie wyprzedzi wolniejszy wskaźnik, jeśli istnieje pętla na połączonej liście, ujawniając istnienie pętli.
Metoda rekurencyjna: Ta metoda przechodzi przez linkowaną listę, wzywając się w kółko. Połączona lista zawiera pętlę, jeśli obecny węzeł został wcześniej odwiedzany.
Podejście oparte na stosie: To podejście przechowuje adresy odwiedzonych węzłów w stosie. Pętla na listy połączonej jest obecna, jeśli węzeł jest już na stosie, gdy zostanie ponownie odwiedzony.
Wyjaśnijmy każde podejście szczegółowo, aby zrozumieć koncepcję.
Podejście 1: Podejście do hashset
Wykorzystanie mieszania jest najprostszą metodą. Tutaj przechodzimy listę jeden po drugim, zachowując tabelę skrótów z adresami węzłów. Dlatego pętla istnieje, jeśli kiedykolwiek przebiegniemy na następny adres obecnego węzła, który jest już zawarty w tabeli skrótów. W przeciwnym razie nie ma pętli na listy powiązanej, jeśli wpadniemy na NULL (i.mi., Dotrzyj na koniec linkowanej listy).
Wdrożenie tej strategii będzie dość łatwe.
Podczas przemierzania listy połączonej, skorzystamy z UNOPOREDED_HASHMAP i nadal dodajemy do niej węzły.
Teraz, gdy natkniemy się na węzeł, który jest już pokazany na mapie, będziemy wiedzieć, że dotarliśmy na początek pętli.
W ten sposób węzeł końcowy pętli zamiast odnosić się do początkowego węzła pętli, zaczyna wskazywać na NULL.
Średni czas i złożoność przestrzeni metody mieszania wynosi O (N). Czytelnik powinien zawsze mieć świadomość, że wdrożenie wbudowanej danych ze mieszania w języku programowania określi całkowitą złożoność czasu w przypadku kolizji w haszku.
Implementacja programu C ++ dla powyższej metody (Hashset):
#włączać
za pomocą przestrzeni nazw Std;
Node struct
wartość int;
Node struct* następny;
;
Węzeł* newNode (wartość int)
Węzeł* tempnode = nowy węzeł;
tempNode-> wartość = wartość;
tempnode-> następny = null;
return TempNode;
// Zidentyfikuj i wyeliminuj wszelkie potencjalne pętle
// na połączonej liście z tą funkcją.
void funkcjaHashmap (węzeł* headnode)
// utworzył UNOPORDED_MAP, aby zaimplementować mapę skrótu
UNOPORDED_MAPhash_map;
// wskaźnik do ostatniego
Węzeł* lastNode = null;
When (Headnode != Null)
// Jeśli na mapie brakuje węzła, dodaj go.
if (hash_map.Znajdź (headnode) == HASH_MAP.koniec())
hash_map [headnode] ++;
lastNode = HeadNode;
HeadNode = HeadNode-> następny;
// Jeśli cykl jest obecny, ustaw następny wskaźnik węzła końcowego na NULL.
w przeciwnym razie
lastNode-> następny = null;
przerwa;
// Wyświetl listę połączoną
Display void (węzeł* headnode)
When (Headnode != Null)
CoutHeadNode = HeadNode-> następny;
Cout << endl;
/* Główna funkcja*/
int main ()
Węzeł* headNode = newNode (48);
HeadNode-> następny = headNode;
HeadNode-> Next = NewNode (18);
HeadNode-> następny-> następny = newNode (13);
HeadNode-> następny-> następny-> następny = newNode (2);
HeadNode-> następny-> następny-> następny-> następny = newNode (8);
/ * Utwórz pętlę w LinkedList */
HeadNode-> następny-> następny-> następny-> następny-> następny = HeadNode-> następny-> następny;
funkcjahashmap (headNode);
printf („Lista linkowana bez pętli \ n”);
wyświetlacz (headnode);
powrót 0;
Wyjście:
Połączona lista bez pętli
48 18 13 2 8
Wyjaśnienie kodu krok po kroku podano poniżej:
Podejście 2: Cykl Floyda
Algorytm wykrywania cyklu Floyda, często znany jako algorytm żółwia i zając, stanowi podstawę tej metody lokalizacji cykli na połączonej liście. „Wolny” wskaźnik i „szybki” wskaźnik, który przemierzają listę przy różnych prędkościach, to dwa wskaźniki używane w tej technice. Szybki wskaźnik rozwija dwa kroki, podczas gdy powolny wskaźnik rozwija jeden krok w każdej iteracji. Cykl na połączonej liście istnieje, jeśli dwa punkty kiedykolwiek pojawią się twarzą w twarz.
1. Z węzłem głównym listy powiązanej zainicjujemy dwa wskaźniki o nazwie Fast and Slow.
2. Teraz uruchamiamy pętlę, aby przejść przez połączoną listę. Szybki wskaźnik powinien zostać przeniesiony do dwóch lokalizacji przed powolnym wskaźnikiem na etapie każdej iteracji.
3. Nie będzie pętli na połączonej liście, jeśli szybki wskaźnik osiągnie koniec listy (fastPointer == null lub fastPointer-> następny == null). Jeśli nie, szybkie i powolne wskaźniki ostatecznie się spełnią, co oznacza, że powiązana lista ma pętlę.
Implementacja programu C ++ dla powyższej metody (cykl Floyda):
#włączać
za pomocą przestrzeni nazw Std;
/ * Węzeł listy linków */
Node struct
dane int;
Node struct* następny;
;
/* Funkcja usuwania pętli. */
void DeleTeLoop (node struct*, struct node*);
/* Ta funkcja lokalizuje i eliminuje pętle list. Daje 1
Jeśli na liście była pętla; w przeciwnym razie zwraca 0. */
Int DetectandDeleTeLoop (lista węzłów struct))
struct node *lista slowptr =, *fastptr = lista;
// iteruj, aby sprawdzić, czy pętla jest tam.
while (slowptr && fastptr && fastptr-> następny)
SULLPTR = SULLPTR-> następny;
fastPtr = fastPtr-> następny-> następny;
/* Jeśli w pewnym momencie spotykają się powolne i fastptr
jest pętlą */
if (slowptr == fastptr)
DeleTeLoop (Slowptr, List);
/* Zwróć 1, aby wskazać, że wykryto pętlę. */
zwrot 1;
/* Zwróć 0, aby wskazać, że nie odkryto żadnej pętli.*/
powrót 0;
/* Funkcja usuwania pętli z listy połączonej.
Loopnode wskazuje na jeden z węzłów pętli, a wskazuje
do węzła początkowego linkowanej listy */
void DeleTeLoop (Node struct LOOPNODE, Node struct* HeadNode)
struct node* ptr1 = loopnode;
struct node* ptr2 = loopnode;
// Policz, ile węzłów jest w pętli.
niepodpisany int k = 1, i;
podczas gdy (ptr1-> następny != ptr2)
ptr1 = ptr1-> następny;
K ++;
// Napraw jeden wskaźnik do headnode
ptr1 = headnode;
// i drugi wskaźnik do k węzłów po headnode
ptr2 = headnode;
dla (i = 0; i < k; i++)
ptr2 = ptr2-> następny;
/* Gdy oba punkty są przenoszone jednocześnie,
zderzyją się w początkowym węźle pętli. */
podczas gdy (ptr2 != ptr1)
ptr1 = ptr1-> następny;
ptr2 = ptr2-> następny;
// Uzyskaj ostatni wskaźnik węzła.
While (ptr2-> następny != ptr1)
ptr2 = ptr2-> następny;
/* Aby zamknąć pętlę, ustaw kolejne
węzeł do węzła kończącego pętlę. */
ptr2-> następny = null;
/ * Funkcja wyświetlania połączonej listy */
void displayLinkedList (węzeł struct*)
// Wyświetl linkowaną listę po usunięciu pętli
While (węzeł != Null)
Cout node = węzeł-> następny;
Node struct* newNode (klucz int)
struct node* temp = new node ();
temp-> data = klucz;
temp-> następny = null;
Return Temp;
// Kod główny
int main ()
struct node* headNode = newNode (48);
HeadNode-> Next = NewNode (18);
HeadNode-> następny-> następny = newNode (13);
HeadNode-> następny-> następny-> następny = newNode (2);
HeadNode-> następny-> następny-> następny-> następny = newNode (8);
/ * Utwórz pętlę */
HeadNode-> następny-> następny-> następny-> następny-> następny = HeadNode-> następny-> następny;
// Wyświetl pętlę na listy połączonej
// displayLinkedList (headNode);
detectandDeleTeLoop (headnode);
Cout << "Linked List after no loop \n";
displayLinkedList (HeadNode);
powrót 0;
Wyjście:
Połączona lista po braku pętli
48 18 13 2 8
Wyjaśnienie:
Podejście 3: Rekurencja
Rekursiona jest techniką rozwiązywania problemów poprzez podzielenie ich na mniejsze, łatwiejsze podproblemy. Rekurencja może być używana do przemierzania pojedynczo połączonej listy w przypadku, gdy pętla jest wykonywana przez ciągłe uruchamianie funkcji dla następnego węzła na liście, aż do osiągnięcia końca listy.
Na pojedynczo połączonej liście podstawową zasadą za pomocą Recursion w celu znalezienia pętli jest rozpoczęcie na czele listy, zaznacz bieżący węzeł odwiedzany na każdym kroku, a następnie przejdź do następnego węzła, rekursywnie wywołując funkcję dla funkcji dla ten węzeł. Metoda będzie iterowana w pełnej listy połączonej, ponieważ nazywa się ją rekurencyjnie.
Lista zawiera pętlę, jeśli węzeł, który był wcześniej oznaczony jako odwiedzany, jest napotkany przez funkcję; W takim przypadku funkcja może zwrócić prawdziwe. Metoda może zwrócić false, jeśli osiągnie koniec listy bez biegania przez odwiedzony węzeł, co wskazuje, że nie ma pętli.
Chociaż ta technika wykorzystywania rekurencji do znalezienia pętli na jednej połączonej liście jest prosta w użyciu i zrozumieniu, może nie być najskuteczniejsza pod względem złożoności czasu i przestrzeni.
Wdrożenie programu C ++ dla powyższej metody (Recursion):
#włączać
za pomocą przestrzeni nazw Std;
Node struct
dane int;
Węzeł* następny;
Bool odwiedził;
;
// Funkcja wykrywania pętli powiązanej
Bool detectLoopLinkedList (węzeł* headnode)
if (headnode == null)
zwrócić fałsz; // Jeśli lista połączona jest pusta, podstawowy przypadek
// istnieje pętla, jeśli ma bieżący węzeł
// już odwiedzano.
if (headnode-> odwiedzone)
zwrócić true;
// Dodaj znak wizyty do bieżącego węzła.
HeadNode-> odwiedzone = true;
// wywoływanie kodu dla kolejnego węzła wielokrotnie
return detectLoopLinkedList (HeadNode-> następny);
int main ()
Node* headNode = new node ();
Węzeł* secondNode = new node ();
Węzeł* trzecinode = nowy node ();
HeadNode-> data = 1;
HeadNode-> następny = secondNode;
HeadNode-> odwiedzone = false;
secondNode-> data = 2;
secondNode-> następny = trzecinode;
secondNode-> odwiedzone = false;
trzecinode-> data = 3;
trzecinode-> next = null; // Brak pętli
trzecinode-> odwiedzony = false;
if (detectLoopLinkedList (headNode))
Cout << "Loop detected in linked list" << endl;
w przeciwnym razie
Cout << "No loop detected in linked list" << endl;
// Tworzenie pętli
trzecinode-> następny = secondNode;
if (detectLoopLinkedList (headNode))
Cout << "Loop detected in linked list" << endl;
w przeciwnym razie
Cout << "No loop detected in linked list" << endl;
powrót 0;
Wyjście:
Brak pętli wykrytych na powiązanej liście
Pętla wykryta na powiązanej liście
Wyjaśnienie:
Podejście 4: Korzystanie z stosu
Pętle na połączonej liście można znaleźć za pomocą stosu i metody „Wyszukiwarka głębokości” (DFS). Podstawową koncepcją jest iteracja za pośrednictwem listy powiązanej, wciskając każdy węzeł na stos, jeśli jeszcze go nie odwiedzono. Pętla jest rozpoznawana, jeśli węzeł, który jest już na stosie.
Oto krótki opis procedury:
Implementacja programu C ++ dla powyższej metody (stos)
#włączać
#włączać
za pomocą przestrzeni nazw Std;
Node struct
dane int;
Węzeł* następny;
;
// funkcja wykrywania pętli na połączonej liście
Bool detectLoopLinkedList (węzeł* headnode)
stosstos;
Węzeł* tempnode = headNode;
While (tempnode != Null)
// Jeśli górny element stosu pasuje
// bieżący węzeł i stos nie jest pusty
Jeśli (!stos.pusty () && Stack.top () == tempnode)
zwrócić true;
stos.push (tempnode);
tempNode = tempnode-> następny;
zwrócić fałsz;
int main ()
Node* headNode = new node ();
Węzeł* secondNode = new node ();
Węzeł* trzecinode = nowy node ();
HeadNode-> data = 1;
HeadNode-> następny = secondNode;
secondNode-> data = 2;
secondNode-> następny = trzecinode;
trzecinode-> data = 3;
trzecinode-> next = null; // Brak pętli
if (detectLoopLinkedList (headNode))
Cout << "Loop detected in linked list" << endl;
w przeciwnym razie
Cout << "No loop detected in linked list" << endl;
// Tworzenie pętli
trzecinode-> następny = secondNode;
if (detectLoopLinkedList (headNode))
Cout << "Loop detected in linked list" << endl;
w przeciwnym razie
Cout << "No loop detected in linked list" << endl;
Wyjście:
Brak pętli wykrytych na powiązanej liście
Pętla wykryta na powiązanej liście
Wyjaśnienie:
Ten program używa stosu, aby dowiedzieć się, czy pojedynczo połączona lista ma pętlę.
2. Standardowa przestrzeń nazw jest zawarta w drugim wierszu, co pozwala nam uzyskać dostęp do funkcji biblioteki strumienia wejściowego/wyjściowego bez konieczności prefiksowania ich za pomocą „STD ::."
3. Poniższy wiersz określa węzeł struktury, który składa się z dwóch członków: liczby całkowitej zwanej „danymi” i wskaźnika do innego węzła o nazwie „Dalej."
4. Głowa listy połączonej jest wejściem dla metody detectLoopLinkedList (), która jest zdefiniowana w następnym wierszu. Funkcja wytwarza wartość logiczną, która wskazuje, czy znaleziono pętlę.
5. Stos wskaźników węzłów zwanych „stosem” i wskaźnik do węzła o nazwie „Tempnode”, który jest inicjowany z wartością nody.
6. Wtedy, dopóki TempNode nie jest wskaźnikiem zerowym, wchodzimy w pętlę.
(a) Górny element stosu musi pasować do bieżącego węzła, abyśmy mogli ustalić, że nie jest on pusty. Zwracamy prawdę, jeśli tak jest, ponieważ znaleziono pętlę.
(b) Jeśli wyżej wymieniony warunek jest fałszywy, bieżący wskaźnik węzła jest wciśnięty na stos, a tempNode jest ustawiony na następujący węzeł na listy połączonej.
7. Wracamy fałsz po pętli, ponieważ nie zaobserwowano pętli.
8. Budujemy trzy obiekty węzłów i inicjujemy je w funkcji Main (. Ponieważ w pierwszym przykładzie nie ma pętli, prawidłowo ustawiamy wskazówki „następne”.
Wniosek:
Podsumowując, najlepsza metoda wykrywania pętli na połączonej liście zależy od konkretnego przypadku użycia i ograniczeń problemu. Tabela hash i algorytm cyklu Floyda są wydajnymi metodami i są szeroko stosowane w praktyce. Stos i rekurencja są mniej wydajnymi metodami, ale są bardziej intuicyjne.