Enkeltforbundet liste i datastrukturer

โšก Smart opsummering

En enkeltkoblet liste er en lineรฆr, ensrettet datastruktur, hvor hver node lagrer data og en enkelt pointer til den nรฆste node, sรฅ gennemlรธbet kun bevรฆger sig fra top til hale, og hukommelse allokeres dynamisk, efterhรฅnden som nye noder tilfรธjes.

  • ๐Ÿงฉ Knudestruktur: Hver node indeholder รฉt datafelt og รฉt nรฆste peger til den fรธlgende node; haleknudens nรฆste pointeren er NULL.
  • ๐Ÿ“ฆ Liste vs. array: Enkeltlรฆnkede lister foretrรฆkkes, nรฅr antallet af elementer er ukendt, tilfรฆldig adgang ikke er pรฅkrรฆvet, og indsรฆttelse midt i listen er almindelig.
  • โž• Indsรฆttelser: Noder kan tilfรธjes i spidsen, i halen, efter en matchet node eller fรธr en matchet node ved hjรฆlp af nรฆste-pointer-omskrivninger.
  • โž– Sletninger: Fjernelse af hovedet, halen eller en sรธgt node opdaterer nabopointere og frigรธr den frigjorte hukommelse for at undgรฅ lรฆkager.
  • ๐Ÿ” Gennemgang: Kun fremadgรฅende gennemgang understรธttes, fordi der ikke er nogen tidligere pointer, sรฅ baglรฆns gennemgang af en enkeltstรฅende linket liste er ikke mulig.
  • ๐Ÿ’ป C++ og Python Code: Komplette implementeringer viser indsรฆttelses-, sletnings-, sรธge- og gennemlรธbsrutiner med kรธrbart output.
  • ๐Ÿ“Š kompleksitet: Indsรฆttelse eller sletning af hoved er O(1); sรธgning og andre indsรฆttelser og sletninger er O(n); rumkompleksitet er O(n).

Enkeltforbundet liste

Hvad er en enkeltstรฅende liste?

En enkeltstรฅende linket liste er en lineรฆr og ensrettet datastruktur, hvor data gemmes pรฅ noderne, og hver node er forbundet via et link til sin nรฆste node. Hver node indeholder et datafelt og et link til den nรฆste node. Enkeltstรฅende linkede lister kan kun gennemlรธbes i รฉn retning, hvorimod en Dobbeltforbundet liste kan gennemkรธres i begge retninger.

Her er nodestrukturen for en enkeltstรฅende linket liste:

Struktur af en node i en sammenkรฆdet liste

Struktur af en node i en sammenkรฆdet liste

Hvorfor bruge en linket liste frem for et array?

Flere scenarier favoriserer en linket liste frem for en Array:

  • Ukendt antal elementer: Nรฅr det nรธdvendige antal elementer ikke er kendt pรฅ kompileringstidspunktet, allokerer en linket liste hukommelse dynamisk, efterhรฅnden som elementer tilfรธjes.
  • Tilfรฆldig adgang: Nรฅr tilfรฆldig indekseret adgang ikke er nรธdvendig, er en linket liste et passende valg.
  • Indsรฆttelse i midten: Indsรฆttelse midt i et array krรฆver forskydning af elementer. En linket liste tillader indsรฆttelse pรฅ en hvilken som helst position ved kun at omskrive et par pointere.

Operationer af Singly Linked List

En enkeltstรฅende linket liste er god til dynamisk allokering af hukommelse. Den understรธtter standardoperationerne for den linkede liste, dvs. indsรฆttelse, sletning, sรธgning, opdatering, sammenlรฆgning af to lister og gennemgang.

Fรธlgende operationer diskuteres i denne artikel:

  • Indfรธring ved hovedet
  • Indfรธring ved hale
  • Indsรฆttelse efter en node
  • Indsรฆttelse fรธr en node
  • Slet hovedknuden
  • Slet haleknuden
  • Sรธg og slet en node
  • Gennemgang af den linkede liste

Her er et eksempel pรฅ en linket liste med fire noder.

Eksempel pรฅ en enkeltstรฅende liste

Eksempel pรฅ en enkeltstรฅende liste

Indsรฆttelse i toppen af โ€‹โ€‹en enkeltstรฅende linket liste

Dette er en simpel operation. Det er generelt kendt som at pushe til en enkeltstรฅende linket liste. En ny node oprettes og placeres รธverst pรฅ listen.

For at udfรธre denne operation skal du fรธlge to vigtige betingelser:

  1. Hvis listen er tom, bliver den nyoprettede node hovednoden, og dens nรฆste pointeren er NULL.
  2. Hvis listen ikke er tom, bliver den nye node hovednoden, og dens nรฆste Markรธren peger pรฅ den forrige hovedknude.

Her er pseudokoden til at indsรฆtte en node i toppen af โ€‹โ€‹en linket liste:

function insertAtHead(head, value):
  newNode = Node(value)
  if head is NULL:
    head = newNode
    return head
  else:
    newNode.next = head
    return newNode

Indsรฆttelse ved hovedet

Indsรฆttelse ved hovedet

Indsรฆttelse i slutningen af โ€‹โ€‹en enkeltstรฅende linket liste

Indsรฆttelse af en node i slutningen af โ€‹โ€‹en linket liste svarer til at indsรฆtte i toppen. Gรฅ til den bageste node, og peg derefter dens nรฆste pegeren til den nye node. Hvis head er NULL, bliver den nye node til head.

Trin 1) Gennemgรฅ indtil nรฆste Pointeren for den aktuelle node bliver NULL.

Trin 2) Opret en ny node med den angivne vรฆrdi.

Trin 3) Tildel den nye knude som den nรฆste knude pรฅ haleknuden.

Pseudokoden til indsรฆttelse i slutningen af โ€‹โ€‹en enkelt liste:

function insertAtEnd(head, value):
  newNode = Node(value)
  if head is NULL:
    head = newNode
    return head
  while head.next is not NULL:
    head = head.next
  head.next = newNode
  newNode.next = NULL

Indfรธring ved Halen

Indfรธring ved halen

Indsรฆttelse efter en node i en enkeltstรฅende linket liste

Indsรฆttelse efter en node har to dele: sรธg efter mรฅlnoden og tilfรธj en ny node efter den. Gennemgรฅ listen, indtil der findes et match, og splejs derefter den nye node ind.

Trin 1) Gennemgรฅ indtil vรฆrdien af โ€‹โ€‹den aktuelle node er lig med sรธgeelementet.

Trin 2) Indstil den nye nodes nรฆste peger til den aktuelle nodes nรฆste markรธr.

Trin 3) Peg pรฅ den nuvรฆrende nodes nรฆste pegeren til den nye node.

Pseudokode:

function insertAfter(head, value, searchItem):
  newNode = Node(value)
  while head.value != searchItem:
    head = head.next
  newNode.next = head.next
  head.next = newNode

Indsรฆttelse af en node efter en node i enkeltforbundet liste

Indsรฆttelse af en node efter en node i Singly Linked List

Indsรฆttelse fรธr en node i en enkeltstรฅende linket liste

Dette svarer til indsรฆttelse efter en node. Gรฅ gennem sรธgefeltet, indtil den nรฆste node matcher sรธgevรฆrdien, og indsรฆt derefter den nye node fรธr den.

Trin 1) Kรธr indtil den nรฆste nodes vรฆrdi er lig med sรธgeelementet.

Trin 2) Opret en ny node og indstil dens nรฆste peger til den aktuelle nodes nรฆste.

Trin 3) Peg pรฅ den nuvรฆrende nodes nรฆste til den nye node.

function insertBefore(head, value, searchItem):
  newNode = Node(value)
  while head.next.value != searchItem:
    head = head.next
  newNode.next = head.next
  head.next = newNode

Indsรฆttelse af en node fรธr en node i enkeltforbundet liste

Indsรฆttelse af en node fรธr en node i Singly Linked List

Slet overskriften pรฅ den enkeltvis sammenkรฆdede liste

Hovedpointen angives som parameter. Hovednoden fjernes, og den nรฆste node bliver den nye hovednode. Hukommelsen for den slettede node skal frigรธres for at undgรฅ hukommelseslรฆkager.

Trin 1) Tildel den nรฆste node i hovedet som det nye hoved.

Trin 2) Frigรธr den allokerede hukommelse fra den forrige head node.

Trin 3) Returner den nye hovedknude.

function deleteHead(head):
  temp = head
  head = head.next
  free(temp)
  return head

Sletning af hovedet pรฅ en sammenkรฆdet liste

Sletning af hovedet pรฅ en linket liste

Slet halen af โ€‹โ€‹den enkeltvis sammenkรฆdede liste

Sletning af halenoden svarer til sletning af hovednoden. Forskellen er, at det er nรธdvendigt at gรฅ til slutningen af โ€‹โ€‹listen. I en enkeltstรฅende linket liste er den node, hvis nรฆste pointeren er NULL er haleknuden.

Trin 1) Gรฅ indtil lige fรธr haleknuden. Gem den aktuelle node.

Trin 2) Frigรธr hukommelsen til den nรฆste node (halen).

Trin 3) Sรฆt den nรฆste node i den aktuelle node til NULL.

function deleteTail(head):
  while head.next.next is not NULL:
    head = head.next
  free(head.next)
  head.next = NULL

Sletning af halen af โ€‹โ€‹enkeltforbundet liste

Sletning af halen af โ€‹โ€‹enkeltforbundet liste

Sรธg og slet en node fra en enkeltstรฅende linket liste

Denne funktion udfรธrer to opgaver: sรธgning og sletning. Naviger til slutningen af โ€‹โ€‹listen. Hvis der findes en matchende node, skal du fjerne den og linke den forrige nodes sammen igen. nรฆste markรธr.

Trin 1) Gรฅ til slutningen af โ€‹โ€‹listen. Kontroller, om den aktuelle node er lig med sรธgenoden.

Trin 2) Hvis der findes et match, gemmes en pointer til den aktuelle node.

Trin 3) nรฆste for den forrige node bliver den nรฆste node for den nuvรฆrende node.

Trin 4) Slet den aktuelle node og frigรธr dens hukommelse.

function searchAndDelete(head, searchItem):
  while head.next.next is not NULL and head.next.value != searchItem:
    head = head.next
  temp = head.next
  head.next = head.next.next
  free(temp)

Sรธg og slet en node fra en enkelt linket liste

Sรธg og slet en node fra Liste med enkelt lรฆnker

Gennemgรฅ en enkeltstรฅende linket liste

En enkeltstรฅende linket liste understรธtter kun gennemgang fra top til hale. Der er ingen pointer til den forrige node, sรฅ omvendt gennemgang er ikke mulig. Hver node besรธges efter tur, og dens vรฆrdi udskrives, indtil NULL nรฅs.

Trin 1) Gennemlรธb hver node, indtil NULL er nรฅet.

Trin 2) Udskriv vรฆrdien af โ€‹โ€‹den aktuelle node.

function traverse(head):
  while head is not NULL:
    print head.value
    head = head.next

Eksempel pรฅ enkeltforbundet liste i C++

#include<iostream>
using namespace std;
struct Node{
  int data;
  struct Node *next;
};
void insertAtHead(Node* &head, int value){
  Node* newNode = new Node();
  newNode->data = value;
  newNode->next = NULL;
  if(head != NULL){
    newNode->next = head;
  }
  head = newNode;
  cout<<"Added "<<newNode->data<<" at the front"<<endl;
}
void insertEnd(Node* &head, int value){
  if(head == NULL){
    insertAtHead(head, value);
    return;
  }
  Node* newNode = new Node();
  newNode->data = value;
  newNode->next = NULL;
  Node *temp = head;
  while(temp->next != NULL){
    temp = temp->next;
  }
  temp->next = newNode;
  cout<<"Added "<<newNode->data<<" at the end"<<endl;
}
void searchAndDelete(Node **headPtr, int searchItem){
  Node *temp = NULL;
  if((*headPtr)->data == searchItem){
    temp = *headPtr;
    *headPtr = (*headPtr)->next;
    free(temp);
  } else {
    Node *currentNode = *headPtr;
    while(currentNode->next != NULL){
      if(currentNode->next->data == searchItem){
        temp = currentNode->next;
        currentNode->next = currentNode->next->next;
        free(temp);
        break;
      } else {
        currentNode = currentNode->next;
      }
    }
  }
  cout<<"Deleted Node\t"<<searchItem<<endl;
}
void insertAfter(Node* &headPtr, int searchItem, int value){
  Node* newNode = new Node();
  newNode->data = value;
  newNode->next = NULL;
  Node *head = headPtr;
  while(head->next != NULL && head->data != searchItem){
    head = head->next;
  }
  newNode->next = head->next;
  head->next = newNode;
  cout<<"Inserted "<<value<<" after node\t"<<searchItem<<endl;
}
void insertBefore(Node* &headPtr, int searchItem, int value){
  Node* newNode = new Node();
  newNode->data = value;
  newNode->next = NULL;
  Node *head = headPtr;
  while(head->next != NULL && head->next->data != searchItem){
    head = head->next;
  }
  newNode->next = head->next;
  head->next = newNode;
  cout<<"Inserted "<<value<<" before node\t"<<searchItem<<endl;
}
void traverse(Node *headPointer){
  Node* tempNode = headPointer;
  cout<<"Traversal from head:\t";
  while(tempNode != NULL){
    cout<<tempNode->data;
    if(tempNode->next)
      cout<<" --> ";
    tempNode = tempNode->next;
  }
  cout<<endl;
}
int main(){
  Node *head = NULL;
  insertAtHead(head, 5);
  insertAtHead(head, 6);
  insertAtHead(head, 7);
  insertEnd(head, 9);
  traverse(head);
  searchAndDelete(&head, 6);
  traverse(head);
  insertAfter(head, 7, 10);
  insertBefore(head, 9, 11);
  traverse(head);
}

Produktion

Added 5 at the front
Added 6 at the front
Added 7 at the front
Added 9 at the end
Traversal from head:    7 --> 6 --> 5 --> 9
Deleted Node    6
Traversal from head:    7 --> 5 --> 9
Inserted 10 after node  7
Inserted 11 before node 9
Traversal from head:    7 --> 10 --> 5 --> 11 --> 9

Eksempel pรฅ enkeltforbundet liste i Python

class Node:
  def __init__(self, data=None, next=None):
    self.data = data
    self.next = next
class SinglyLinkedList:
  def __init__(self):
    self.head = None
  def insertAtHead(self, value):
    newNode = Node(data=value)
    if self.head is not None:
      newNode.next = self.head
    self.head = newNode
    print(f'Added {newNode.data} at the front.')
  def insertAtEnd(self, value):
    if self.head is None:
      self.insertAtHead(value)
      return
    newNode = Node(value)
    temp = self.head
    while temp.next is not None:
      temp = temp.next
    temp.next = newNode
    print(f'Added {newNode.data} at the end.')
  def searchAndDelete(self, searchItem):
    if self.head is None:
      return
    if self.head.data == searchItem:
      self.head = self.head.next
      print(f'Deleted node\t{searchItem}')
      return
    currentNode = self.head
    while currentNode.next is not None:
      if currentNode.next.data == searchItem:
        currentNode.next = currentNode.next.next
        print(f'Deleted node\t{searchItem}')
        return
      currentNode = currentNode.next
  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
    print(f'Inserted {value} after node\t{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
    print(f'Inserted {value} before node\t{searchItem}')
  def traverse(self):
    temp = self.head
    print("Traversing from head:\t", end="")
    while temp:
      print("{}\t".format(temp.data), end="")
      temp = temp.next
    print()
singlyLinkedList = SinglyLinkedList()
singlyLinkedList.insertAtHead(5)
singlyLinkedList.insertAtHead(6)
singlyLinkedList.insertAtHead(7)
singlyLinkedList.insertAtEnd(9)
singlyLinkedList.traverse()
singlyLinkedList.searchAndDelete(6)
singlyLinkedList.traverse()
singlyLinkedList.insertAfter(7, 10)
singlyLinkedList.insertBefore(9, 11)
singlyLinkedList.traverse()

Produktion

Added 5 at the front.
Added 6 at the front.
Added 7 at the front.
Added 9 at the end.
Traversing from head:   7       6       5       9
Deleted node    6
Traversing from head:   7       5       9
Inserted 10 after node  7
Inserted 11 before node 9
Traversing from head:   7       10      5       11      9

Kompleksiteten af โ€‹โ€‹enkeltforbundet liste

Der er to typer kompleksitet: tidskompleksitet og rumkompleksitet. Den vรฆrste og gennemsnitlige tidskompleksitet er den samme for en enkeltstรฅende linket liste.

Bedste-case tidskompleksitet:

  • Indsรฆttelse ved toppen kan udfรธres i O(1). Ingen gennemgang inden for listen er nรธdvendig.
  • Sรธgning og sletning kan udfรธres i O(1), hvis mรฅlelementet er ved hovednoden.

Gennemsnitlig sagstidskompleksitet:

  • Indsรฆttelse i en linket liste krรฆver O(n), hvor n er det samlede antal elementer.
  • Sรธgning og sletning kan ogsรฅ tage O(n), fordi mรฅlelementet kan befinde sig hvor som helst op til haleknuden.

Rumkompleksitet af enkelttilknyttede lister

En enkeltstรฅende linket liste allokerer dynamisk hukommelse. For at gemme n elementer, den allokerer n hukommelsesenheder. Sรฅ rumkompleksiteten er O(n).

Anvendelser af enkeltstรฅende linkede lister

Enkeltforbundne lister vises mange steder, hvor kun fremadrettet gennemgang og dynamisk hukommelse er nyttige:

  • Stakke og kรธer: Underliggende lagring til LIFO-stakke og FIFO-kรธer bygget fra noder.
  • Hash-tabelkรฆde: Kollisioner lรธses ved at kรฆde poster sammen i en enkeltstรฅende linket liste pr. bucket.
  • Nรฆrliggende lister: Sparse grafer bruger en enkeltstรฅende linket liste over naboer for hvert hjรธrne.
  • Symboltabeller: Compilere og fortolkere kรฆder identifikatorer sammen til en enkeltstรฅende linket liste pr. scope.
  • Hukommelsesallokatorer: Gratislistetildelere track frie blokke som en enkeltstรฅende linket liste.

Ofte Stillede Spรธrgsmรฅl

Enkeltforbundne lister kรฆder trรฆningsprรธver, minibatches og frie hukommelsesblokke i AI-frameworks, hvilket muliggรธr dynamiske kรธer til streaminginput og lรฅsefri datapipelines, der skalerer med modellens efterspรธrgsel.

Ja. GitHub Copilot og GPT kan producere en komplet enkeltstรฅende linket liste i C, C++, Java, Python eller JavaScript, inklusive indsรฆttelse, sletning, tilbagefรธrsel, cyklusdetektion og enhedstest.

En enkeltlรฆnket liste har รฉn nรฆste-pointer og bevรฆger sig kun fremad. En dobbeltlรฆnket liste har bรฅde nรฆste- og forrige-pointere og bevรฆger sig i begge retninger, men bruger mere hukommelse pr. node.

Almindelige anvendelser inkluderer stak- og kรธimplementeringer, hash-tabelkรฆdering, adjacency-lister til sparse grafer, symboltabeller i compilere, free-list-allokatorer og fortrydelseshistorik i letvรฆgtseditorer.

Indsรฆttelse eller sletning ved hovedet er O(1). Indsรฆttelse ved halen, sรธgning, indsรฆttelse pรฅ en position og sletning af en specifik node koster alle O(n), fordi gennemlรธb er pรฅkrรฆvet fra hovedet.

Lรฆnkede lister vokser og krymper under kรธrsel, indsรฆttes eller slettes i O(1), nรฅr positionen er kendt, og behรธver aldrig sammenhรฆngende hukommelse. Arrays tilbyder O(1) tilfรฆldig adgang og bedre cache-lokalitet.

Gรฅ gennem listen med tre markรธrer, prev, curr og next. Gem curr.next i hvert trin, peg curr.next pรฅ prev, og flyt prev og curr fremad. Returner prev som den nye overskrift.

Floyds skildpadde-og-hare-algoritme bruger to pointere, der bevรฆger sig med forskellige hastigheder. Hvis de nogensinde mรธdes, indeholder listen en cyklus. Ellers nรฅr den hurtige pointer NULL, og der findes ingen cyklus.

Opsummer dette indlรฆg med: