Inden for datalogi er en datastruktur en organiseret måde at lagre og organisere værdier og information på, så man kan udføre operationer effektivt. Med enkle ord er en datastruktur en metode til at organisere data for at optimere ydeevne, hukommelsesforbrug eller enkelhed i koden. Datastrukturer adskiller sig fra abstrakte datatyper ved, at de er konkrete implementeringer: abstrakte datatyper beskriver hvilke operationer der kan udføres (fx en liste), mens datastrukturer beskriver, hvordan disse operationer realiseres i hukommelsen. Implementeringen af en datastruktur foretages typisk vha. algoritmer, f.eks. relationen mellem en abstrakt liste og en sammenkædet liste (linked list), hvor hver node indeholder en værdi og en reference (pointer) til det næste — eventuelt også til det forrige — element.

Hvad karakteriserer en datastruktur?

En datastruktur karakteriseres bl.a. ved:

  • Støttede operationer: indsæt, slet, søg, opdater, iteration mv.
  • Tidskompleksitet: hvor hurtigt operationerne kan udføres (ofte angivet i Big O-notation).
  • Pladsforbrug: hvor meget hukommelse datastrukturen kræver, inkl. metadata som pointere eller indeks.
  • Stabilitet og concurrency-egenskaber: hvordan datastrukturen opfører sig ved samtidig adgang i multithreadede miljøer.

Typiske typer af datastrukturer

Her er en oversigt over almindelige datastrukturer og deres typiske anvendelser:

  • Array (vektor): kontinuerlig blok af elementer med konstant tid for adgang via indeks. God til tilfældig adgang, mindre god til indsættelse/sletning midt i strukturen.
  • Sammenkædede lister (linked lists): fleksibel ved indsættelse/sletning, men langsommere tilfældig adgang pga. sekventiel traversal.
  • Stack og kø (stack/queue): specialiserede lister til LIFO- og FIFO-adfærd; bruges i bl.a. rekursion, parsing og job-planlægning.
  • Træer (fx binære søgetræer, AVL, rød-sorte træer): organiserer data hierarkisk, gør søgning, indsættelse og sletning effektive (logaritmisk tid) når træet er balanceret.
  • Heap (prioritetskø): effektiv til at finde og fjerne elementet med højeste eller laveste prioritet (fx prioriterede job, Dijkstras algoritme).
  • Hashtabeller: giver gennemsnitlig konstant tid for indsætning, søgning og sletning ved hjælp af en hashfunktion; bruges til opslagstabeller og caches.
  • Grafer: bruges til at modellere netværk (fx sociale netværk, veje, kommunikationssystemer); repræsenteres ofte vha. adjacency-lists eller adjacency-matrices.
  • Trie (præfiks-træ): optimeret til opslag af strenge og præfikssøgninger (fx autokomplettering).

Implementering og algoritmer

Når man implementerer datastrukturer, vælger man mellem forskellige teknikker afhængigt af sprog og krav:

  • Kontinuerlig hukommelse: arrays og dynamiske arrays (fx std::vector i C++ eller ArrayList i Java) bruger sammenhængende hukommelsesblokke, hvilket giver god cache-udnyttelse.
  • Dynamisk allokering og pointere/referencer: sammenkædede strukturer og træer bruger pointere eller referencer til at forbinde noder, hvilket gør dem fleksible mht. vækst men kan medføre ekstra hukommelsesoverhead.
  • Hashing: kræver en god hashfunktion og håndtering af kollisioner (åben adressering eller kædning).
  • Balancering: selvbalancerende træer (AVL, rød-sorte) anvender rotationer og andre operationer for at sikre, at dybden forbliver logaritmisk.

Valg af datastruktur

At vælge den rigtige datastruktur er ofte en af de vigtigste designbeslutninger i udviklingen af en løsning. Overvej:

  • Hvilke operationer er mest hyppige? Hvis søgning er hyppig, kan hashtabel eller balanceret træ være bedst. Hvis hyppige indsættelser/sletninger i midten er vigtige, kan en sammenkædet liste være passende.
  • Begrænsninger i hukommelse og realtid: i indlejrede systemer eller realtidsprogrammer kan plads- og tidsgarantier bestemme valget.
  • Enkelhed vs. ydeevne: nogle gange er en simpel løsning (fx array eller liste) tilstrækkelig og lettere at vedligeholde end en kompleks, optimeret datastruktur.
  • Sprog og bibliotekstilbud: moderne sprog leverer ofte robuste standarddatastrukturer (fx Collections i Java, STL i C++ eller dict/list i Python) som bør genbruges, medmindre der er særlige krav.

Tids- og pladscomplexitet (Big O)

For hver datastruktur vurderes operationernes kompleksitet. Eksempler (typisk tilfælde):

  • Array: adgang O(1), indsæt/slet i midten O(n)
  • Sammenkædet liste: adgang O(n), indsæt/slet ved node O(1)
  • Hashtabel: søg/indsæt/slet O(1) gennemsnitligt, værre tilfælde O(n)
  • Binært søgetræ: søg/indsæt/slet O(h) hvor h er træhøjden (i gennemsnit O(log n) for balancerede træer)
  • Heap: indsættelse O(log n), fjern den højeste prioritet O(log n)

Praktiske eksempler og anvendelser

  • Web- og databaser: hashtabeller til caches og indeks; B-træer i databaser for effektiv diskbaseret opslag.
  • Søgning og ruteplanlægning: grafer og prioritetskøer (heap) i algoritmer som Dijkstra og A*.
  • Tekstbehandling: tries og suffix-arrays til mønstergenkendelse og autokomplettering.
  • Systemprogrammering: arrays og linked lists i kernen, heaps til planlægning af processer.

Konklusion

Datastrukturer er grundstenen i effektiv programmering: de definerer hvordan data opbevares og hvordan algoritmer kan operere på dem. Valget af datastruktur påvirker både ydeevne og kompleksiteten af en løsning. Derfor er forståelsen af de mest almindelige datastrukturer, deres fordele, ulemper og implementeringer et centralt element i programmeringen.