Dvostruko povezani popis: C++, Python (Code Primjer)
โก Pametni saลพetak
Dvostruko povezana lista je linearna struktura podataka u kojoj svaki ฤvor pohranjuje podatke plus dva pokazivaฤa, jedan na prethodni ฤvor i jedan na sljedeฤi ฤvor, tako da se prolaz moลพe uฤinkovito kretati i naprijed i natrag.

ล to je dvostruko povezana lista?
U dvostruko povezanoj listi, svaki ฤvor ima veze i na prethodni i na sljedeฤi ฤvor. Svaki ฤvor sastoji se od tri elementa: jedan sadrลพi podatke, a druga dva su pokazivaฤi na sljedeฤi i prethodni ฤvor. Ova dva pokazivaฤa pomaลพu u kretanju naprijed ili natrag od odreฤenog ฤvora.
Evo osnovne strukture dvostruko povezane liste.
Struktura dvostruko povezane liste
Svaka povezana lista ima poฤetni i krajnji ฤvor. Poฤetni ฤvor nema pret (prethodni pokazivaฤ) ฤvor, a repni ฤvor nema sljedeฤi ฤvor.
Evo nekoliko vaลพnih pojmova za dvostruko povezane liste:
- Prethodna: Svaki ฤvor je povezan sa svojim prethodnim ฤvorom. Koristi se kao pokazivaฤ ili poveznica.
- Sljedeฤi: Svaki ฤvor je povezan sa svojim sljedeฤim ฤvorom. Koristi se kao pokazivaฤ ili poveznica.
- Podaci: Ovo se koristi za pohranu podataka u ฤvoru. Podaci mogu sadrลพavati i druge Strukture podataka unutar njega. Na primjer, string, rjeฤnik, skup, hashmap i druge strukture mogu se pohraniti u podatkovno polje.
Evo osnovne strukture jednog ฤvora u dvostruko povezanoj listi:
Struktura ฤvora u dvostruko povezanoj listi
Operacije dvostruko povezanog popisa
Operacije dvostruko povezane liste ukljuฤuju dodavanje, brisanje, umetanje i uklanjanje ฤvorova, kao i kretanje po listi od vrha prema dnu ili od dna prema vrhu.
Evo popisa operacija koje se mogu implementirati na dvostruko povezanoj listi:
- Umetanje ispred
- Umetanje na repu ili posljednjem ฤvoru
- Umetanje nakon ฤvora
- Umetanje prije ฤvora
- Brisanje sprijeda
- Brisanje iz repa
- Pretraลพivanje i brisanje ฤvora
- Traverza od glave do repa
- Preฤite repom u glavu
Implementacija i pseudokod za svaku od ovih operacija slijede u nastavku.
Umetanje ispred dvostruko povezane liste
Umetanje ispred znaฤi stvaranje ฤvora u povezanoj listi i njegovo postavljanje na poฤetak liste.
Na primjer, postoji zadani ฤvor 15Potrebno ga je dodati kao glavni ฤvor.
Prilikom izvoฤenja ove operacije primjenjuju se dva vaลพna uvjeta:
- Novi ฤvor postaje glavni ฤvor ako je dvostruko povezana lista prazna.
- Ako veฤ postoji glavni ฤvor, prethodni glavni ฤvor zamjenjuje se novim ฤvorom.
Evo pseudokoda za ovu operaciju:
function insertAtFront(ListHead, value): newNode = Node() newNode.value = value ListHead.prev = newNode newNode.next = ListHead newNode.prev = NULL return ListHead
Umetanje u prednji ฤvor
Umetanje na kraj dvostruko povezane liste
Umetanje na kraj znaฤi stvaranje ฤvora u povezanoj listi i njegovo postavljanje na rep.
Ovu operaciju izvode dvije metode:
- Metoda 1: Poฤnite pregledavati od poฤetka dvostruko povezane liste sve do sljedeฤi postaje null. Zatim poveลพite novi ฤvor s sljedeฤi pokazivaฤ.
- Metoda 2: Uzmite posljednji ฤvor dvostruko povezane liste. Zatim, sljedeฤi Pokazivaฤ zadnjeg ฤvora pokazuje na novi ฤvor. Novi ฤvor postaje repni ฤvor.
Evo pseudokoda za umetanje na repnom ฤvoru:
function insertAtTail(ListHead, value): newNode = Node() newNode.value = value newNode.next = NULL while ListHead.next is not NULL: ListHead = ListHead.next newNode.prev = ListHead ListHead.next = newNode return ListHead
Umetanje na kraj povezanog popisa
Umetanje nakon ฤvora
Razmotrite postojeฤu dvostruko povezanu listu kao ลกto je sljedeฤa:
Cilj je umetnuti zadani ฤvor koji ฤe biti povezan nakon ฤvora s vrijednoลกฤu 12.
Korak 1) Prijeฤite od poฤetka do zadnjeg ฤvora. Provjerite koji ฤvor ima vrijednost 12.
Korak 2) Stvorite novi ฤvor i dodijelite ga kao sljedeฤi pokazivaฤ ฤvora 12, sljedeฤi ฤvor novog ฤvora bit ฤe 15.
Evo pseudokoda za umetanje ฤvora nakon ฤvora u dvostruko povezanoj listi:
function insertAfter(ListHead, searchItem, value): List = ListHead newNode = Node() newNode.value = value while List.value is not equal searchItem: List = List.next newNode.next = List.next newNode.prev = List List.next = newNode
Umetanje nakon ฤvora
Umetanje prije ฤvora
Ova operacija je sliฤna umetanju nakon ฤvora. Pretraลพuje se odreฤena vrijednost ฤvora, zatim se stvara novi ฤvor i umeฤe prije traลพenog ฤvora.
Za umetanje odreฤenog ฤvora 15 prije ฤvora 12, prati ove korake:
Korak 1) Preฤite povezanim popisom od glavnog ฤvora do repnog ฤvora.
Korak 2) Provjeri ima li sljedeฤi pokazivaฤ trenutnog ฤvora vrijednost 12.
Korak 3) Umetnite novi ฤvor kao sljedeฤi ฤvor trenutnog ฤvora.
Evo pseudokoda za umetanje ฤvora prije ฤvora u dvostruko povezanoj listi:
function insertBefore(ListHead, searchItem, value): List = ListHead newNode = Node() newNode.value = value while List.next.value is not equal searchItem: List = List.next newNode.next = List.next newNode.prev = List List.next = newNode
Umetanje ฤvora ispred ฤvora
Izbriลกi zaglavlje dvostruko povezane liste
Glavni ฤvor u dvostruko povezanoj listi nema prethodnih ฤvorova. Dakle, sljedeฤi Pokazivaฤ postaje novi glavni ฤvor kada se trenutni glavni ฤvor ukloni. Takoฤer je potrebno osloboditi memoriju koju zauzima izbrisani ฤvor.
Evo koraka za brisanje glavnog ฤvora:
Korak 1) Dodijelite varijablu trenutnom glavnom ฤvoru.
Korak 2) Posjetite sljedeฤi ฤvor trenutnog glavnog ฤvora i napravite pret pokazivaฤ NULL. Ovo odvaja drugi ฤvor od prvog ฤvora.
Korak 3) Oslobodite memoriju koju je zauzimao prethodni glavni ฤvor.
Evo pseudokoda za brisanje glave iz dvostruko povezane liste:
function deleteHead(ListHead): PrevHead = ListHead ListHead = ListHead.next ListHead.prev = NULL PrevHead.next = NULL free memory(PrevHead) return ListHead
Brisanje glavnog ฤvora
Nakon bilo kakvog brisanja potrebno je osloboditi dodijeljenu memoriju. U suprotnom, memorija za izbrisani blok ostaje zauzeta tijekom cijelog izvoฤenja programa i nijedna druga aplikacija ne moลพe koristiti taj segment memorije.
Izbriลกi rep dvostruko povezane liste
Ova operacija je sliฤna brisanju glave. Umjesto glave, uklanja se rep. Da bi se ฤvor identificirao kao rep, provjerite je li sljedeฤi pokazivaฤ null. Nakon brisanja repa, memorija se mora osloboditi.
Ova operacija je takoฤer poznata kao brisanje s leฤa.
Evo koraka kako to uฤiniti:
Korak 1) Pomiฤite se do repnog ฤvora dvostruko povezane liste.
Korak 2) Dodijelite varijablu ili pokazivaฤ repnom ฤvoru.
Korak 3) Postavi sljedeฤi pokazivaฤ na NULL i oslobodi memoriju repnog ฤvora.
Evo pseudokoda za brisanje repnog ฤvora:
function deleteTail(ListHead): head = ListHead while ListHead.next is not NULL: ListHead = ListHead.next Tail = ListHead ListHead.prev.next = NULL free memory(Tail) return head
Pretraลพivanje i brisanje ฤvora s dvostruko povezane liste
Ova operacija traลพi odreฤenu vrijednost ฤvora i briลกe taj ฤvor. Linearno pretraลพivanje je potrebno jer je povezani popis linearna struktura podataka. Nakon brisanja, memorija se mora osloboditi.
Evo koraka za pretraลพivanje i brisanje ฤvora u dvostruko povezanoj listi:
Korak 1) Prolazite kroz povezanu listu od poฤetka dok vrijednost ฤvora ne bude jednaka traลพenoj stavci.
Korak 2) Dodijeli varijablu brisanje ฤvora do odgovarajuฤeg ฤvora.
Korak 3) Poveลพi prethodni ฤvor od brisanje ฤvora na sljedeฤi ฤvor i postavi sljedeฤi ฤvor pret pokazivaฤ na prethodni ฤvor.
Korak 4) Oslobodite sjeฤanje na brisanje ฤvora.
Evo pseudokoda za pretraลพivanje i brisanje ฤvora s povezane liste:
function searchAndDelete(ListHead, searchItem): head = ListHead while head.value not equals searchItem: head = head.next deleteNode = head head.prev.next = head.next if head.next is not NULL: head.next.prev = head.prev free memory(deleteNode) return ListHead
Operacija pretraลพivanja i brisanja
Prolazak kroz dvostruko povezanu listu od naprijed
Prijelaz od glavnog ฤvora iterira preko sljedeฤeg ฤvora dok se ne pronaฤe NULL. Tijekom prelaska kroz svaki ฤvor, vrijednost se moลพe ispisati. Evo koraka za prelazak u smjeru naprijed:
Korak 1) Dodijelite pokazivaฤ ili varijablu trenutnom glavnom ฤvoru.
Korak 2) Iteriraj do sljedeฤeg ฤvora glave dok ne dobijeลก NULL.
Korak 3) Ispiลกite podatke o ฤvorovima u svakoj iteraciji.
Korak 4) Vratite glavni ฤvor.
Evo pseudokoda za obilazak dvostruko povezane liste s poฤetka:
function traverseFromFront(ListHead): head = ListHead while head not equals NULL: print head.data head = head.next return ListHead
Povratak nije obavezan. Meฤutim, vraฤanje glavnog ฤvora nakon operacija je dobra praksa.
Prolazak kroz dvostruko povezanu listu od unatrag
Ova operacija je inverzna od kretanja sprijeda. Pristup je isti s jednom malom razlikom: prvo doฤite do krajnjeg ฤvora, a zatim hodajte unatrag do vrha koristeฤi pret pokazivaฤ.
Evo koraka za obilazak dvostruko povezane liste s leฤa:
Korak 1) Kreฤite se dok se ne doฤe do repnog ฤvora.
Korak 2) Od repnog ฤvora, preฤite pomoฤu pret dok prethodni ฤvor ne postane NULL. pret Pokazivaฤ je null za glavni ฤvor.
Korak 3) U svakoj iteraciji, ispiลกite podatke o ฤvoru.
Evo pseudokoda za prelazak s leฤa:
function traverseFromBack(ListHead): head = ListHead while head.next is not NULL: head = head.next tail = head while tail is not NULL: print tail.value tail = tail.prev return ListHead
Razlika izmeฤu jednostruko i dvostruko povezane liste
Glavna razlika izmeฤu jednostruko povezane liste i dvostruko povezane liste je broj veza koje svaki ฤvor sadrลพi.
Evo razlike izmeฤu ฤvorova jednostruko povezane liste i dvostruko povezane liste:
| Polje | Pojedinaฤno povezani popis | Dvostruko povezana lista |
|---|---|---|
| Struktura | Pojedinaฤno povezani popis ima jedno podatkovno polje i jednu vezu na sljedeฤi ฤvor. | Dvostruko povezani popis ima jedno podatkovno polje i dvije veze. Jedan za prethodni ฤvor i drugi za sljedeฤi ฤvor. |
| obuhvaฤanje | Moลพe iฤi samo od glave do repa. | Moลพe se kretati i naprijed i natrag. |
| memorija | Zauzima manje memorije. | Zauzima viลกe memorije od jednostruko povezane liste. |
| Pristupaฤnost | Jednostruko povezane liste su manje uฤinkovite jer koriste samo jednu vezu do sljedeฤeg ฤvora. Ne postoji veza do prethodnog ฤvora. | Dvostruko povezane liste su uฤinkovitije od jednostruko povezanih lista za dvosmjerni pristup. |
Dvostruko povezani popis u C++
Ispod je potpuni C++ Implementacija dvostruko povezane liste s operacijama umetanja, brisanja, pretraลพivanja i pomicanja.
#include<iostream> using namespace std; struct node{ int data; struct node *next; struct node *prev; }; void insertFront(node* &listHead, int value){ node* newNode = new node(); newNode->data = value; newNode->prev = NULL; newNode->next = NULL; if(listHead != NULL){ listHead->prev = newNode; newNode->next = listHead; } listHead = newNode; cout<<"Added "<<value<<" at the front"<<endl; } void insertEnd(node* &listHead, int value){ if(listHead == NULL){ insertFront(listHead, value); return; } node* newNode = new node(); newNode->data = value; newNode->prev = NULL; newNode->next = NULL; node *head = listHead; while(head->next != NULL){ head = head->next; } head->next = newNode; newNode->prev = head; cout<<"Added "<<value<<" at the end"<<endl; } void insertAfter(node* &listHead, int searchValue, int value){ node* newNode = new node(); newNode->data = value; newNode->prev = NULL; newNode->next = NULL; node *head = listHead; while(head->next != NULL && head->data != searchValue){ head = head->next; } newNode->next = head->next; head->next = newNode; newNode->prev = head; if(newNode->next != NULL){ newNode->next->prev = newNode; } cout<<"Inserted "<<value<<" after node "<<searchValue<<endl; } void insertBefore(node* &listHead, int searchValue, int value){ node* newNode = new node(); newNode->data = value; newNode->prev = NULL; newNode->next = NULL; node *head = listHead; while(head->next != NULL && head->next->data != searchValue){ head = head->next; } newNode->next = head->next; head->next = newNode; newNode->prev = head; if(newNode->next != NULL){ newNode->next->prev = newNode; } cout<<"Inserted "<<value<<" before node "<<searchValue<<endl; } void traverseFromFront(node *listHead){ node* head = listHead; cout<<"Traversal from head:\t"; while(head != NULL){ cout<<head->data<<"\t"; head = head->next; } cout<<endl; } void traverseFromEnd(node *listHead){ node* head = listHead; cout<<"Traversal from tail:\t"; while(head->next != NULL){ head = head->next; } node *tail = head; while(tail != NULL){ cout<<tail->data<<"\t"; tail = tail->prev; } cout<<endl; } void searchAndDelete(node **listHead, int searchItem){ node* head = (*listHead); while(head != NULL && head->data != searchItem){ head = head->next; } if(*listHead == NULL || head == NULL) return; if((*listHead)->data == head->data){ *listHead = head->next; } if(head->next != NULL){ head->next->prev = head->prev; } if(head->prev != NULL){ head->prev->next = head->next; } free(head); cout<<"Deleted Node\t"<<searchItem<<endl; } int main(){ node *head = NULL; insertFront(head, 5); insertFront(head, 6); insertFront(head, 7); insertEnd(head, 9); insertEnd(head, 10); insertAfter(head, 5, 11); insertBefore(head, 5, 20); traverseFromFront(head); traverseFromEnd(head); searchAndDelete(&head, 7); traverseFromFront(head); traverseFromEnd(head); }
Izlaz
Added 5 at the front Added 6 at the front Added 7 at the front Added 9 at the end Added 10 at the end Inserted 11 after node 5 Inserted 20 before node 5 Traversal from head: 7 6 20 5 11 9 10 Traversal from tail: 10 9 11 5 20 6 7 Deleted Node 7 Traversal from head: 6 20 5 11 9 10 Traversal from tail: 10 9 11 5 20 6
Dvostruko povezani popis u Python
Ispod je potpuni Python implementacija dvostruko povezane liste koriลกtenjem klasa za ฤvorove i same liste.
class Node: def __init__(self, data=None, prev=None, next=None): self.data = data self.next = next self.prev = prev class DoublyLinkedList: def __init__(self): self.head = None def insertFront(self, val): newNode = Node(data=val) newNode.next = self.head if self.head is not None: self.head.prev = newNode self.head = newNode print("Added {} at the front".format(val)) def insertEnd(self, val): newNode = Node(data=val) if self.head is None: self.head = newNode print("Added {} at the end".format(val)) return temp = self.head while temp.next is not None: temp = temp.next temp.next = newNode newNode.prev = temp print("Added {} at the end".format(val)) def traverseFromFront(self): temp = self.head print("Traversing from head:\t", end="") while temp is not None: print("{}\t".format(temp.data), end="") temp = temp.next print() def traverseFromEnd(self): temp = self.head print("Traversing from tail:\t", end="") while temp.next is not None: temp = temp.next tail = temp while tail is not None: print("{}\t".format(tail.data), end="") tail = tail.prev print() def insertAfter(self, searchItem, value): newNode = Node(data=value) temp = self.head while temp.next is not None and temp.data != searchItem: temp = temp.next newNode.next = temp.next temp.next = newNode newNode.prev = temp if newNode.next is not None: newNode.next.prev = newNode print("Inserted {} after node {}".format(value, searchItem)) def insertBefore(self, searchItem, value): newNode = Node(data=value) temp = self.head while temp.next is not None and temp.next.data != searchItem: temp = temp.next newNode.next = temp.next temp.next = newNode newNode.prev = temp if newNode.next is not None: newNode.next.prev = newNode print("Inserted {} before node {}".format(value, searchItem)) def searchAndDelete(self, searchItem): temp = self.head while temp is not None and temp.data != searchItem: temp = temp.next if self.head is None or temp is None: return if self.head.data == temp.data: self.head = temp.next if temp.next is not None: temp.next.prev = temp.prev if temp.prev is not None: temp.prev.next = temp.next print("Deleted Node\t{}".format(searchItem)) doublyLinkedList = DoublyLinkedList() doublyLinkedList.insertFront(5) doublyLinkedList.insertFront(6) doublyLinkedList.insertFront(7) doublyLinkedList.insertEnd(9) doublyLinkedList.insertEnd(10) doublyLinkedList.insertAfter(5, 11) doublyLinkedList.insertBefore(5, 20) doublyLinkedList.traverseFromFront() doublyLinkedList.traverseFromEnd() doublyLinkedList.searchAndDelete(7) doublyLinkedList.traverseFromFront() doublyLinkedList.traverseFromEnd()
Izlaz
Added 5 at the front Added 6 at the front Added 7 at the front Added 9 at the end Added 10 at the end Inserted 11 after node 5 Inserted 20 before node 5 Traversing from head: 7 6 20 5 11 9 10 Traversing from tail: 10 9 11 5 20 6 7 Deleted Node 7 Traversing from head: 6 20 5 11 9 10 Traversing from tail: 10 9 11 5 20 6
Sloลพenost dvostruko povezane liste
Vremenska sloลพenost se opฤenito dijeli na tri vrste: najbolji sluฤaj, prosjeฤni sluฤaj i najgori sluฤaj.
Vremenska sloลพenost u najboljem sluฤaju za dvostruko povezani popis:
- Umetanje na poฤetku ili kraju liste koลกta O(1) jer nije potreban prolaz unutar povezane liste. Pokazivaฤi na poฤetku i kraju liste daju izravan pristup poฤetnim i krajnjim ฤvorovima.
- Brisanje na poฤetku ili kraju skupa koลกta O(1).
- Pretraลพivanje ฤvora koลกta O(1) kada je ciljni ฤvor glavni ฤvor.
Vremenska sloลพenost u prosjeฤnom sluฤaju za dvostruko povezani popis:
- Umetanje na poฤetku ili kraju koลกta O(1).
- Brisanje na poฤetku ili kraju skupa koลกta O(1).
- Pretraลพivanje ฤvora koลกta O(n), jer se cilj moลพe nalaziti bilo gdje na popisu. Ovdje, n je ukupan broj ฤvorova.
Vremenska sloลพenost dvostruko povezane liste u najgorem sluฤaju ista je kao i u prosjeฤnom sluฤaju.
Memorijska sloลพenost dvostruko povezanog popisa
Sloลพenost memorije je O(n), gdje je n je ukupan broj ฤvorova. Tijekom implementacije povezane liste, memorija se mora osloboditi. Inaฤe, veฤe povezane liste uzrokuju curenje memorije.
Primjene dvostruko povezane liste
Dvostruko povezane liste pokreฤu nekoliko stvarnih struktura podataka jer dvosmjerni prolaz pojednostavljuje mnoge uobiฤajene operacije.
- LRU predmemorija: Najmanje koriลกtene predmemorije koriste dvostruko povezanu listu s hash mapom za O(1) premjeลกtanje na poฤetak i deloลพiranje.
- Povijest preglednika: Navigacija naprijed i natrag omoguฤuje kretanje po povezanom popisu u oba smjera.
- Poniลกti i ponovi nizove: Urednici i IDE-ovi track verzija dokumenata s pokazivaฤima na prethodno i sljedeฤe.
- Deque: DoubleRedovi s krajevima se guraju i otvaraju s oba kraja u vremenu O(1).
- Glazbene liste za reprodukciju: Prethodno i sljedeฤe tracGumbi k oslanjaju se na pokazivaฤe naprijed i natrag.











