Duplán linkelt lista: C++, Python (Példa a kódra)

Mi az a duplán linkelt lista?

Egy duplán linkelt listában minden csomópont rendelkezik hivatkozással az előző és a következő csomópontra is. Minden csomópont három elemből áll: az egyik az adatokat tartalmazza, a másik kettő pedig a következő és az előző csomópont mutatója. Ez a két mutató segít egy adott csomóponttól előre vagy hátrafelé haladni.

Íme a duplán linkelt lista alapstruktúrája.

Duplán linkelt lista felépítése
Duplán linkelt lista felépítése

Minden csatolt listának van fej- és farokcsomópontja. A fej csomópontnak nincs „előző” (előző mutató) csomópontja, és a farok csomópontnak nincs „következő” csomópontja.

Íme néhány fontos kifejezés egy duplán linkelt listához:

  • Előző: Minden csomópont kapcsolódik az előző csomópontjához. Mutatóként vagy hivatkozásként használják.
  • Következő: Minden csomópont a következő csomóponthoz kapcsolódik. Mutatóként vagy hivatkozásként használják.
  • Adatok: Ez adatok tárolására szolgál egy csomópontban. Az „adatok” mást is tartalmazhatnak Adatstruktúrák benne. Például karakterlánc, szótár, halmaz, hashmap stb. tárolható az „Adatok” között.

Íme egy csomópont alapstruktúrája a duplán linkelt listában:

Csomópont felépítése duplán linkelt listában

Csomópont szerkezete duplán linkelt listában

OperaKettős hivatkozású lista

A duplán linkelt lista műveletei közé tartozik a csomópontok hozzáadása és törlése, csomópontok beszúrása és eltávolítása, valamint a hivatkozott lista bejárása felülről lefelé.

Íme azoknak a műveleteknek a listája, amelyeket a duplán linkelt lista segítségével végrehajthatunk:

  • Beillesztés elöl
  • Beillesztés a farokba vagy az utolsó csomópontba
  • Beillesztés egy csomópont után
  • Beillesztés egy csomópont elé
  • Törlés elölről
  • Törlés a farokból
  • Keressen és töröljön egy csomópontot
  • Fejtől farokig haladva
  • Fordítsd a farkát a fejhez

Látni fogjuk az összes fenti művelet megvalósítását és pszeudokódját.

Beszúrás a Duplán linkelt lista elé

Az elülső beillesztés azt jelenti, hogy létre kell hoznunk egy csomópontot a hivatkozott listában, és el kell helyeznünk a hivatkozott lista elejére.

Például van egy adott „15” csomópont. Ezt hozzá kell adnunk a fejcsomóponthoz.

Íme két fontos feltétel a művelet végrehajtása során:

  1. Az új csomópont a fő csomópont lesz, ha nincs csomópont a Duplán linkelt listában.
  2. Ha már van fejcsomópont, az előző fejet felváltja az új csomópont.

Íme a művelet pszeudokódja:

function insertAtFront (ListHead, value):
  newNode = Node()
  newNode.value = value
  ListHead.prev = NewNode
  NewNode.next = ListHead
  newNode.prev = NULL
  return ListHead
Beillesztés az elülső csomópontba
Beillesztés az elülső csomópontba

Beszúrás a Duplán linkelt lista végére

A „Beszúrás a végén” azt jelenti, hogy létrehozunk egy csomópontot a hivatkozott listában, és a végére helyezzük.

Ennek végrehajtásához két módszert használhatunk:

  • 1 módszer: Kezdje el a bejárást a Duplán linkelt lista fejétől, amíg a „következő” nullává nem válik. Ezután kapcsolja össze az új csomópontot a „következővel”.
  • 2 módszer: Vegyük a Duplán linkelt lista utolsó csomópontját. Ezután az utolsó csomópont „következő” csomópontja kapcsolódik az új csomóponthoz. Most az új csomópont a végcsomópont lesz.

Íme a pszeudokód a farok csomópontba történő beszúráshoz:

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
Beszúrás a linkelt lista végére

Beillesztés a linkelt lista végére

Beillesztés egy csomópont után

Tegyük fel, hogy van egy duplán linkelt listánk, például a következő:

Beillesztés egy csomópont után

Egy adott csomópontot szeretnénk beszúrni, amely a „12” értékű csomópont után lesz csatolva.

Íme a lépések ehhez:

Step 1) Haladjon át a fejtől az utolsó csomópontig. Ellenőrizze, hogy melyik csomópont értéke „12”.

Step 2) Hozzon létre egy új csomópontot, és rendelje hozzá a „12” csomópont következő mutatójaként. Az új csomópont „következő” csomópontja a 15.

Íme a pszeudokód egy csomópont beszúrásához egy duplán linkelt listában lévő csomópont után:

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
Beillesztés egy csomópont után

Beillesztés egy csomópont után

Beillesztés egy csomópont elé

Ez a művelet jobban hasonlít a „csomópont utáni beszúráshoz”. Ehhez meg kell keresnünk egy adott csomópont értékét. Ezután létrehozunk egy új csomópontot, és beillesztjük a keresett csomópont elé.

Tegyük fel, hogy egy adott „15” csomópontot szeretnénk beszúrni a „12” csomópont elé. Akkor íme a lépések ennek végrehajtásához:

Step 1) Haladjon át a hivatkozott listán a fő csomóponttól a végcsomópontig.

Step 2) Ellenőrizze, hogy az aktuális csomópont következő mutatójának értéke „12” vagy sem.

Step 3) Szúrja be az új csomópontot az aktuális csomópont „következő” csomópontjaként.

Íme a pszeudokód egy csomópont beszúrásához egy csomópont elé a duplán linkelt listában:

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
Csomópont beszúrása csomópont elé

Csomópont beszúrása csomópont elé

Törölje a duplán linkelt lista fejét

A duplán linkelt lista fejcsomópontja, amelyhez nem tartozik korábbi csomópont. Tehát a következő mutató az új fejcsomópont lesz, ha törölni akarjuk a fejcsomópontot. A csomópont törlése közben a csomópont által elfoglalt memóriát is fel kell szabadítanunk.

A fejcsomópont törlésének lépései a következők:

Step 1) Változó hozzárendelése az aktuális fejcsomóponthoz.

Step 2) Látogassa meg az aktuális fejcsomópont „következő” csomópontját, és tegye az „előző” (előző mutató) csomópontot „NULL” értékre. Ez azt jelenti, hogy leválasztjuk a második csomópontot az első csomópontról.

Step 3) Az előző fejcsomópont által lefoglalt memória felszabadítása.

Íme az pszeudokód a fej törléséhez egy duplán linkelt listából:

function deleteHead(ListHead):
  PrevHead = ListHead
  ListHead = ListHead.next
  ListHead.prev = NULL
  PrevHead.next = NULL
  free memory(PrevHead)
  return ListHead
A fejcsomópont törlése

A fejcsomópont törlése

Bármilyen törlési művelet elvégzése után törölni kell a lefoglalt memóriát. Ellenkező esetben a program teljes futási ideje alatt a törölt blokk memóriája foglalt marad. Más alkalmazás nem tudja használni ezt a memóriaszegmenst.

Törölje a duplán linkelt lista végét.

Ez a művelet nagyjából megegyezik a fej törlésével. Itt a fej helyett a farkot kell törölnünk. Ahhoz, hogy egy csomópontot végcsomópontként azonosítsunk, ellenőriznünk kell, hogy a következő mutató vagy a következő csomópont nulla-e vagy sem. A farok törlése után fel kell szabadítanunk a memóriát.

Ezt a műveletet „hátulról való törlésnek” is nevezik.

Íme a lépések ehhez:

Step 1) Haladjon a duplán linkelt lista végcsomópontjáig.

Step 2) Változó vagy mutató hozzárendelése a végcsomóponthoz.

Step 3) Tegye a „következő” csomópontot NULL-ként, és szabadítsa fel a végcsomópont memóriáját.

Íme a pszeudokód a farok csomópont törléséhez:

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

Törölje a Duplán összekapcsolt farkát

Keressen és töröljön egy csomópontot a Duplán linkelt listából

Ez a művelet lehetővé teszi, hogy egy adott csomópont adatait keressük, és töröljük azt. Lineáris keresést kell végrehajtanunk, mivel a linkelt lista egy lineáris adatstruktúra. A törlés után a memóriát is fel kell szabadítanunk.

Íme a lépések egy csomópont kereséséhez és törléséhez a duplán linkelt listában:

Step 1) Haladjon végig a hivatkozott listán a fejtől egészen addig, amíg a csomópont értéke megegyezik a keresési elemmel.

Step 2) Rendeljen egy „deleteNode” változót az illesztett csomóponthoz.

Step 3) Rendelje hozzá a „deleteNode” előző csomópontját a következő csomóponthoz.

Step 4) Szabadítsa fel a „deleteNode” memóriáját

Íme a pszeudokód a linkelt listából egy csomópont kereséséhez és törléséhez:

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
Keresés és törlés OperaCIÓ

Keresés és törlés művelet

Bejárás egy duplán linkelt listán elölről

A fejcsomópontból való áthaladáshoz és a következő csomóponton való iterációhoz, amíg meg nem találjuk a „NULL”-t. Az egyes csomópontok bejárása közben kinyomtathatjuk a csomópont értékét. Íme a lépések az elölről való haladáshoz (előre):

Step 1) Rendeljen mutatót vagy változót az aktuális fejcsomóponthoz.

Step 2) Iteráljon a fej következő csomópontjához, amíg a „NULL” értéket nem kapja

Step 3) Nyomtassa ki a csomópont adatait minden iterációban.

Step 4) Vissza a fejcsomópontot.

Íme a pszeudokód a kettős hivatkozású lista elölről történő bejárásához:

function traverseFromFront(ListHead):
  head = ListHead
  while head not equals to NULL:
	print head.data
	head = head.next
  return ListHead

Itt a visszaküldés nem kötelező. De jó gyakorlat a fejcsomópont visszaadása a műveletek után.

Menjen át egy duplán linkelt listán visszafelé

Ez a művelet az elölről történő átmenet fordítottja. A megközelítés ugyanaz, kis eltéréssel. Át kell haladnunk a végcsomópontig, majd a végcsomóponttól az elülső csomópontig.

Íme a lépések a duplán linkelt lista hátulról történő bejárásához:

Step 1) Haladjunk addig, amíg el nem érjük a farok csomópontját.

Step 2) A farok csomóponttól addig haladunk, amíg az előző csomópontot „NULL”-ként nem kapjuk. A „prev” (előző mutató) nulla lesz a fejcsomópontnál

Step 3) Minden iterációnál kinyomtatjuk a csomópont adatait.

Íme a pszeudokód a hátulról történő bejáráshoz:

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

Különbség az egyszeri és duplán linkelt lista között

A fő különbség az Singly és a Duplán linkelt lista között a hivatkozások száma.

Különbség az egyszeri és duplán linkelt lista között

Íme a különbség az egyszeresen linkelt lista csomópontjai és a duplán linkelt lista csomópontszerkezete között:

Mező Egyedül linkelt lista Duplán linkelt lista
Szerkezet Egyedül linkelt lista egy adatmezővel és egy hivatkozással rendelkezik a következő csomópontra. A Duplán linkelt lista egy adatmezőt és két hivatkozást tartalmaz. Egy az előző csomóponthoz, egy másik a következő csomóponthoz.
A bejárás Csak fejtől farokig képes áthaladni. Előre és hátra is haladhat.
Memory design Kevesebb memóriát foglal el. Több memóriát foglal el, mint egy külön linkelt lista.
Megközelíthetőség: Az egyszeresen linkelt listák kevésbé hatékonyak, mivel csak egy hivatkozást használnak a következő csomóponthoz. Nincs hivatkozás az előző csomóponthoz. A duplán linkelt listák hatékonyabbak, mint az egyszeresen linkelt listák.

Duplán linkelt lista be 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);
}

teljesítmény

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

Duplán linkelt lista be 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()

teljesítmény

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

A duplán linkelt lista összetettsége

Általában az időbonyolultság három típusra osztható.

Ők:

  1. Legjobb eset
  2. Átlagos eset
  3. Legrosszabb esetben

Időbonyolultság a legjobb esetben a Duplán linkelt lista esetén:

  1. A fejbe vagy a farokba való behelyezés O(1)-be kerül. Mert nem kell bejárnunk a linkelt listán belül. A fej és a farokmutató hozzáférést biztosít számunkra a fej és a farok csomópontjához O(1) időbonyolítással.
  2. A fej vagy a farok törlése O(1)-be kerül.
  3. Egy csomópont keresése O(1) időbonyolultságú lesz. Mert a célcsomópont lehet a fejcsomópont.

Időbeli összetettség átlagos esetben a Duplán linkelt lista esetében:

  1. A fejnél vagy a faroknál történő beillesztés az O(1) költség időbonyolításával jár.
  2. A fejnél vagy a végnél történő törlés az O(1) költség időbonyolításával jár.
  3. Egy csomópont keresése O(n) időbonyolultságú lesz. Mivel a keresett elem a hivatkozott lista között bárhol elhelyezkedhet. Itt az „n” a hivatkozott listában található összes csomópont.

A duplán linkelt lista legrosszabb eseti összetettsége megegyezik az átlagos esettel.

A Duplán linkelt lista memória összetettsége

A memória összetettsége O(n), ahol „n” a csomópontok teljes száma. A linkelt lista megvalósítása során fel kell szabadítani a használt memóriát. Ellenkező esetben egy nagyobb linkelt lista esetén memóriaszivárgást okoz.

Összegzésként

  • A linkelt lista egyfajta lineáris adatstruktúra, lineárisan ábrázolt adatok gyűjteménye.
  • A duplán linkelt lista olyan csatolt lista, amelyben egy csomópont az előző és a következő csomóponthoz is kapcsolódik.
  • A duplán csatolt lista tartalmazza az összes műveletet, például csomópont hozzáadása, csomópont törlése, csomópont beszúrása egy másik csomópont mögé vagy elé, és a hivatkozott lista bejárása a fejtől a végig.
  • A Duplán linkelt lista egy adatmezőt és két hivatkozást tartalmaz. Egy az előző csomóponthoz, egy másik a következő csomóponthoz.
  • A Duplán linkelt lista összetettsége Legjobb eset, Átlagos eset
  • És a legrosszabb eset.