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.

Struktura dvostruko povezane liste
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:

Struktura ฤvora u dvostruko povezanoj listi

Struktura ฤvora u dvostruko povezanoj listi

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:

  1. Novi ฤvor ฤ‡e biti glavni ฤvor ako nema ฤvora na dvostruko povezanom popisu.
  2. 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 u prednji ฤvor
Umetanje u prednji ฤvor

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 na kraj povezanog popisa

Umetanje na kraj povezanog popisa

Umetanje nakon ฤvora

Recimo da imamo postojeฤ‡i dvostruko povezani popis poput sljedeฤ‡eg:

Umetanje nakon ฤvora

ลฝ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 nakon ฤvora

Umetanje nakon ฤvora

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
Umetanje ฤvora ispred ฤvora

Umetanje ฤvora ispred ฤvora

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
Brisanje glavnog ฤvora

Brisanje glavnog ฤvora

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

Izbriลกite rep dvostruko povezanog

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
Pretraลพivanje i brisanje OperaANJE

Operacija pretraลพivanja i brisanja

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.

Razlika izmeฤ‘u jednostruko i dvostruko povezanih popisa

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:

  1. Najbolji sluฤaj
  2. Prosjeฤan sluฤaj
  3. Najgori sluฤaj

Vremenska sloลพenost u najboljem sluฤaju za dvostruko povezani popis:

  1. 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.
  2. Brisanje na ฤelu ili repu koลกtat ฤ‡e O(1).
  3. 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:

  1. Umetanje na ฤelu ili repu imat ฤ‡e vremensku sloลพenost troลกka O(1).
  2. Brisanje na ฤelu ili repu imat ฤ‡e vremensku sloลพenost troลกka O(1).
  3. 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.

Saลพmite ovu objavu uz: