Dvostruko povezani popis: C++, Python (Primjer koda)
ล to je dvostruko povezana lista?
U dvostruko povezanom popisu, svaki ฤvor ima veze i na prethodni i na sljedeฤi ฤvor. Svaki ฤvor se sastoji od tri elementa: jedan sadrลพi podatke, a druga dva su pokazivaฤi sljedeฤeg i prethodnog ฤvora. Ova dva pokazivaฤa nam pomaลพu da idemo naprijed ili nazad od odreฤenog ฤvora.
Ovdje je osnovna struktura dvostruko povezane liste.

Svaki povezani popis ima glavni i zadnji ฤvor. Glavni ฤvor nema "prethodni" (prethodni pokazivaฤ) ฤvor, a repni ฤvor nema "sljedeฤi" ฤvor.
Evo nekoliko vaลพnih pojmova za dvostruko povezani popis:
- 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 ฤvor. "Podaci" mogu sadrลพavati druge Strukture podataka unutar. Na primjer, niz, rjeฤnik, skup, hashmapa itd. mogu se pohraniti u "Podatke".
Ovdje je osnovna struktura jednog ฤvora na dvostruko povezanom popisu:
Operacije dvostruko povezanog popisa
Operacije dvostruko povezanog popisa ukljuฤuju dodavanje i brisanje ฤvorova, umetanje i uklanjanje ฤvorova i prelaลพenje povezanog popisa od vrha do dna.
Evo popisa operacija koje moลพemo implementirati pomoฤu dvostruko povezanog popisa:
- Umetanje ispred
- Umetanje u rep ili zadnji ฤvor
- 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
Vidjet ฤemo implementaciju i pseudokod za sve gore navedene operacije.
Umetanje ispred dvostruko povezane liste
Umetanje ispred znaฤi da moramo stvoriti ฤvor na povezanom popisu i postaviti ga na poฤetak povezanog popisa.
Na primjer, postoji ฤvor "15". Ovo moramo dodati u glavni ฤvor.
Evo dva vaลพna uvjeta pri izvoฤenju ove operacije:
- Novi ฤvor ฤe biti glavni ฤvor ako nema ฤvora na dvostruko povezanom popisu.
- Ako veฤ postoji glavni ฤvor, prethodni ฤe biti zamijenjen 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 na kraj dvostruko povezane liste
"Umetanje na kraju" znaฤi da ฤemo stvoriti ฤvor na povezanoj listi i postaviti ga na kraj.
Da bismo to izveli, moลพemo koristiti dvije metode:
- Metoda 1: Poฤnite prelaziti od glave dvostruko povezane liste dok "sljedeฤe" ne postane null. Zatim poveลพite novi ฤvor sa "sljedeฤim".
- Metoda 2: Uzmite posljednji ฤvor dvostruko povezane liste. Zatim ฤe se "sljedeฤi" ฤvor posljednjeg ฤvora povezati s novim ฤvorom. Sada ฤe novi ฤvor biti repni ฤvor.
Evo pseudokoda za umetanje u repni ฤvor:
function insertAtTail(ListHead, value): newNode = Node() newNode.value = value newNode.next = NULL while ListHead.next is not NULL: then ListHead = ListHead.next newNode.prev = ListHead ListHead.next = newNode return ListHead
Umetanje nakon ฤvora
Recimo da imamo postojeฤi dvostruko povezani popis poput sljedeฤeg:
ลฝelimo umetnuti dati ฤvor koji ฤe biti povezan nakon ฤvora, koji ima vrijednost "12".
Evo koraka za to:
Korak 1) Prijeฤi od glave do posljednjeg ฤvora. Provjerite koji ฤvor ima vrijednost "12".
Korak 2) Napravite 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 povezanu listu:
function insertAfter(ListHead, searchItem, value): List = ListHead NewNode = Node() NewNode.value = value while List.value is not equal searchItem then List = ListHead.next List = List.next NewNode.next = List.next NewNode.prev = List List.next = NewNode
Umetanje prije ฤvora
Ova je operacija sliฤnija "umetanju nakon ฤvora". Moramo potraลพiti vrijednost odreฤenog ฤvora da bismo to izveli. Zatim ฤemo stvoriti novi ฤvor i umetnuti ga ispred traลพenog ฤvora.
Recimo da ลพelimo umetnuti dati ฤvor โ15โ prije ฤvora โ12โ. Zatim evo koraka za to:
Korak 1) Preฤite povezanim popisom od glavnog ฤvora do repnog ฤvora.
Korak 2) Provjerite ima li sljedeฤi pokazivaฤ trenutnog ฤvora vrijednost โ12โ ili ne.
Korak 3) Umetnite novi ฤvor kao "sljedeฤi" ฤvor trenutnog ฤvora.
Evo pseudokoda za umetanje ฤvora ispred ฤvora na dvostruko povezanom popisu:
function insertBefore(ListHead, searchItem, value): List = ListHead NewNode = Node() NewNode.value = value while List.next.value is not equal searchItem then List = ListHead.next NewNode.next = List.next NewNode.prev = List List.next = NewNode
Izbriลกite glavu dvostruko povezane liste
Glavni ฤvor na dvostruko povezanom popisu koji nema prethodni ฤvor. Dakle, sljedeฤi pokazivaฤ ฤe biti novi glavni ฤvor ako ลพelimo izbrisati glavni ฤvor. Takoฤer moramo osloboditi memoriju koju zauzima ฤvor dok briลกemo ฤ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 โprevโ (prethodni pokazivaฤ) ฤvor kao ''NULL''. To znaฤi da odspajamo drugi ฤvor od prvog ฤvora.
Korak 3) Oslobodite memoriju koju je zauzeo prethodni glavni ฤvor.
Evo pseudokoda za brisanje glave s dvostruko povezane liste:
function deleteHead(ListHead): PrevHead = ListHead ListHead = ListHead.next ListHead.prev = NULL PrevHead.next = NULL free memory(PrevHead) return ListHead
Potrebno je izbrisati dodijeljenu memoriju nakon izvoฤenja bilo koje operacije brisanja. U protivnom, tijekom cijelog vremena izvoฤenja programa, memorija za izbrisani blok ostat ฤe zauzeta. Nijedna druga aplikacija ne moลพe koristiti taj segment memorije.
Izbriลกite rep dvostruko povezane liste.
Ova operacija je ista kao i brisanje glave. Ovdje, umjesto glave, moramo izbrisati rep. Da bismo identificirali ฤvor kao repni ฤvor, trebali bismo provjeriti je li sljedeฤi pokazivaฤ ili sljedeฤi ฤvor null ili ne. Nakon brisanja repa, moramo osloboditi memoriju.
Ova operacija je takoฤer poznata kao "brisanje s leฤa".
Evo koraka za to:
Korak 1) Preฤite do repnog ฤvora dvostruko povezane liste.
Korak 2) Dodijelite varijablu ili pokazivaฤ repnom ฤvoru.
Korak 3) Napravite "sljedeฤi" ฤvor kao NULL i oslobodite 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.next ListHead.next = NULL free memory( Tail ) return head
Pretraลพite i izbriลกite ฤvor s dvostruko povezanog popisa
Ova nam operacija omoguฤuje traลพenje podataka odreฤenog ฤvora i brisanje tog ฤvora. Moramo izvrลกiti linearno pretraลพivanje jer je povezani popis linearna struktura podataka. Nakon brisanja trebamo osloboditi i memoriju.
Evo koraka za pretraลพivanje i brisanje ฤvora na dvostruko povezanom popisu:
Korak 1) Preฤite povezanim popisom od glave sve dok vrijednost ฤvora ne bude jednaka stavci pretraลพivanja.
Korak 2) Dodijelite varijablu "deleteNode" odgovarajuฤem ฤvoru.
Korak 3) Dodijelite prethodni ฤvor "deleteNode" sljedeฤem ฤvoru.
Korak 4) Oslobodite memoriju za "deleteNode"
Evo pseudokoda za pretraลพivanje i brisanje ฤvora s povezanog popisa:
function SearchAndDelete(ListHead, searchItem): head = ListHead while ListHead.next.value not equals searchItem: head = head.next deleteNode = head.next head.next = head.next.next head.next.prev = head deleteNode.next, deleteNode.next = NULL free memory(deleteNode) return ListHead
Prelazi dvostruko povezani popis od naprijed
Za kretanje od glavnog ฤvora i ponavljanje preko sljedeฤeg ฤvora dok ne pronaฤemo "NULL". Dok obilazimo svaki ฤvor, moลพemo ispisati vrijednost ฤvora. Evo koraka za kretanje sprijeda (smjer prema naprijed):
Korak 1) Dodijelite pokazivaฤ ili varijablu trenutnom glavnom ฤvoru.
Korak 2) Iterirajte do sljedeฤeg ฤvora glave dok ne dobijete "NULL"
Korak 3) Ispiลกite podatke ฤvora u svakoj iteraciji.
Korak 4) Vratite glavni ฤvor.
Evo pseudokoda za obilazak dvostruko povezanog popisa s prednje strane:
function traverseFromFront(ListHead): head = ListHead while head not equals to NULL: print head.data head = head.next return ListHead
Ovdje vraฤanje nije obavezno. Ali dobra je praksa vratiti ฤvor glave nakon operacije.
Preฤite dvostruko povezanu listu odnatrag
Ova operacija je obratna od prijelaza sprijeda. Pristup je isti s malom razlikom. Moramo prijeฤi do krajnjeg ฤvora i zatim prijeฤi od krajnjeg ฤvora do prednjeg ฤvora.
Evo koraka za obilazak dvostruko povezanog popisa odostraga:
Korak 1) Prelazimo dok ne doฤemo do repnog ฤvora.
Korak 2) Od repnog ฤvora, prelazit ฤemo sve dok prethodni ฤvor ne dobijemo kao "NULL". "prev" (prethodni pokazivaฤ) bit ฤe null za glavni ฤvor
Korak 3) U svakoj iteraciji ispisat ฤemo podatke ฤvora.
Evo pseudo-koda za kretanje odostraga:
function traverseFromBack(ListHead): head = ListHead while head not equals NULL: head = head.next tail = head while tail not equal to NULL: print tail.value tail = tail.prev return ListHead
Razlika izmeฤu jednostruko i dvostruko povezanih popisa
Glavna razlika izmeฤu pojedinaฤnog i dvostruko povezanog popisa je broj poveznica.
Evo razlike izmeฤu ฤvorova jednostruko povezanog popisa i strukture ฤvorova dvostruko povezanog popisa:
| 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 pojedinaฤno povezanog popisa. |
| Pristupaฤnost | Pojedinaฤno povezani popisi manje su uฤinkoviti jer koriste samo jednu vezu do sljedeฤeg ฤvora. Ne postoji poveznica za prethodni ฤvor. | Dvostruko povezani popisi uฤinkovitiji su od jednostruko povezanih popisa. |
Dvostruko povezani popis u C++
#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);
}
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 = new 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 = new node();
head = listHead;
cout<<"Traversal from head:\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 = new node();
head = (*listHead);
while(head->next!=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;
return;
}
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 Aded 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 head: 10 9 11 5 20 6 7 Deleted Node 7 Traversal from head: 6 20 5 11 9 10 Traversal from head: 10 9 11 5 20 6
Dvostruko povezani popis u Python
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.insertFront(val)
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:
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 is not 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 is not 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.next is not None and temp.data is not searchItem:
temp = temp.next
if self.head is None or temp is None:
return
if self.head.data is 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))
return
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
Opฤenito, vremenska sloลพenost se dijeli na tri vrste.
Oni su:
- Najbolji sluฤaj
- Prosjeฤan sluฤaj
- Najgori sluฤaj
Vremenska sloลพenost u najboljem sluฤaju za dvostruko povezani popis:
- Umetanje u glavu ili rep ฤe koลกtati O(1). Jer ne trebamo prelaziti unutar povezanog popisa. Pokazivaฤ glave i repa moลพe nam dati pristup ฤvoru glave i repa s O(1) vremenskom sloลพenoลกฤu.
- Brisanje na ฤelu ili repu koลกtat ฤe O(1).
- Traลพenje ฤvora imat ฤe vremensku sloลพenost O(1). Buduฤi da ciljni ฤvor moลพe biti glavni ฤvor.
Vremenska sloลพenost u prosjeฤnom sluฤaju za dvostruko povezani popis:
- Umetanje na ฤelu ili repu imat ฤe vremensku sloลพenost troลกka O(1).
- Brisanje na ฤelu ili repu imat ฤe vremensku sloลพenost troลกka O(1).
- Traลพenje ฤvora imat ฤe vremensku sloลพenost O(n). Buduฤi da se stavka pretraลพivanja moลพe nalaziti bilo gdje izmeฤu povezanog popisa. Ovdje je "n" ukupni ฤvor prisutan na povezanom popisu.
Vremenska sloลพenost dvostruko povezane liste u najgorem sluฤaju bit ฤe ista kao i prosjeฤni sluฤaj.
Memorijska sloลพenost dvostruko povezanog popisa
Sloลพenost memorije je O(n), gdje je "n" ukupan broj ฤvorova. Tijekom implementacije povezane liste moramo osloboditi memoriju koju smo koristili. U suprotnom, za veฤi povezani popis, to ฤe uzrokovati curenje memorije.
Rezime
- Povezani popis vrsta je linearne strukture podataka, zbirka podataka predstavljenih na linearan naฤin.
- Dvostruko povezani popis vrsta je povezanog popisa gdje ฤvor ima veze i s prethodnim i sa sljedeฤim ฤvorom.
- Dvostruko povezani popis sadrลพi sve operacije kao ลกto su dodavanje ฤvora, brisanje ฤvora, umetanje ฤvora iza ili prije drugog ฤvora i prelaลพenje povezanog popisa od glave do repa.
- Dvostruko povezani popis ima jedno podatkovno polje i dvije veze. Jedan za prethodni ฤvor i drugi za sljedeฤi ฤvor.
- Sloลพenost dvostruko povezane liste Najbolji sluฤaj, prosjeฤan sluฤaj
- I u najgorem sluฤaju.



