Grunnleggende Datastrukturer Lars Vidar Magnusson 7.2.2014 Kapittel 10 Stakker og køer Lenkede lister Pekere og objekter Trerepresentasjoner
Datastrukturer Vi er i gang med tredje del av kurset hvor vi skal se på datastrukturer. I denne forelesningen skal vi se på noen grunnleggende strukturer før vi går videre med mere kompliserte strukturer i de neste forelesningene. Mange av disse bygger på prinsippene og algoritmene presentert i dag.
Dynamiske Sett Dynamiske sett er en viktig del i utviklingen av effektive algoritmer fordi de tilbyr operasjoner for å ivereta et sett med keys (nøkler) og satelittdata. Mange algoritmer krever at man kan utføre grunnleggende operasjoner som innsetting, sletting og medlemskaptesting. Slike sett blir ofte referert til som dictionaries Andre algoritmer krever mere kompliserte operasjoner som f.eks en minimum-prioritets kø.
Operasjoner på Dynamiske Sett Det er normalt å kunne utføre en rekke operasjoner på dynamiske sett. Search(S, k) - en forespørsel som returnerer en peker/referanse til elementet med nøkkel k i settet S, eller nil hvis elementet ikke finnes. Insert(S, x) - setter inn et element x i settet S. Delete(S, x) - sletter elementet x fra settet S. Minimum(S) - returnerer elementet med minste nøkkel i settet S. Maximum(S) - returnerer elementet med største nøkkel i settet S. Successor(S, x) - returnerer det elementet som kommer etter x, sortert i henhold til nøkkelen til x. Predecessor(S, x) - returnerer det elementet som kommer før x, sortert i henhold til nøkkelen til x. Datastrukturene vi skal se på i denne delen av kurset implementerer typiskt et subsett av disse operasjonene.
Stakker Stakker er dynamiske sett som følger en LIFO policy. LIFO (Last-In-First-Out) er en policy som navnet tilsier krever at siste element som blir satt inn skal først ut. Minner om en stabbel med tallerkener i.e. man legger til og tar tallerkener på toppen av stabbelen. En stakk S kan implementeres effektivt med en vanlig array. En stakk S vil til enhver tid ivareta et attributt S.top som peker på siste element satt inn.
Stakk Operasjoner Insetting i stakker kalles typiskt push, og sletting kalles typiskt pop. Push(S, x) 1 S.top = S.top + 1 2 S[S.top] = x Pop(S) 1 if Stack-Empty(S) 2 error underflow 3 else S.top = S.top 1 4 return S[S.top + 1] Stack-Empty(S) 1 if S.top = = 0 2 return true 3 else return false
Hvordan Operasjonene Virker Figuren under viser hvordan en stakk implementert med en array fungerer. (a) viser en stakk etter at noen tilfeldige verdier er satt inn. (b) viser den samme stakken etter at 17 og 3 har blitt satt inn. (c) viser stakken etter at det siste elementet har blitt fjernet.
Køer Køer er dynamiske sett som følger en FIFO policy. LIFO (First-In-First-Out) er en policy som navnet tilsier krever at første element som blir satt inn skal først ut. En kø Q, som med en stakk, kan implementeres effektivt med en vanlig array med wrap-around. En kø Q vil til enhver tid ivereta to attributter Q.head og Q.tail som peker på henholdvis første elementet og den første ledige plassen etter det siste elementet satt inn.
Kø Operasjoner Insetting i køer kalles typiskt enqueue, og sletting kalles typiskt dequeue. Enqueue(Q, x) 1 Q[Q.tail] = x 2 if Q.tail = = Q.length 3 Q.tail = 1 4 else Q.tail = Q.tail + 1 Dequeue(Q) 1 x = Q[Q.head] 2 if Q.head = = Q.length 3 Q.head = 1 4 else Q.head = Q.head + 1 5 return x
Hvordan Operasjonene Virker Figuren under viser hvordan en kø implementert med en array fungerer. (a) viser en kø etter at noen tilfeldige verdier er satt inn. (b) viser den samme køen etter at 17, 3 og 5 har blitt satt inn. (c) viser køen etter at det første elementet har blitt fjernet.
Lenkede Lister Lenkede lister er dynamiske sett som er ordnet i en lineær rekkefølge som arrays. I motsetning til arrays, hvor rekkefølgen er styrt av indeksen i arrayet, så er rekkefølgen i lenkede lister styrt av en peker/referanse i hvert element. Enkeltlenkede lister ivaretar et attributt x. next på hvert element x. Dobbeltlenkede lister ivaretar to attributter på hvert element x, x. next og x. prev. Lenkede lister kan være både sorterte og usorterte. I en sirkulær lenket liste så peker x.prev til siste elementet og x.next i det siste elementet peker på det første. Et attributt L.head peker til første element i listen L, eller til nil hvis listen er tom.
Søking i Lenkede Lister Et søk i en lenket liste er et lineært søk som returnerer første forekomst av et element med nøkkel k i listen L. Algoritmen returnerer nil hvis elementet ikke blir funnet. List-Search(L, k) 1 x = L.head 2 while x nil and x.key k 3 x = x.next 4 return x Kjøretiden til List-Search er i verstefall Θ(n) siden den må gå gjennom hele listen.
Innsetting og Sletting i en Lenket Liste Innsetting og sletting kan gjøres i lenkede lister med henholdsvis List-Insert og List-Delete. List-Insert(L, x) 1 x.next = L.head 2 if L.head nil 3 L.head.prev = x 4 L.head = x 5 x.prev = nil List-Delete(L, x) 1 if x.prev nil 2 x.prev.next = x.next 3 else L.head = x.next 4 if x.next nil 5 x.next.prev = x.prev List-Insert og List-Delete kjører i Θ(1) tid.
Hvordan En Lenket Liste Virker Figuren under viser hvordan en dobbeltlenket liste virker. (a) viser listen etter at noen tilfeldige verdier er satt inn. (b) viser den samme listen etter at 25 er blitt satt inn. (c) viser listen etter at det elementet 4 har blitt fjernet.
Lenkede Lister og Sentinels Vi kan forenkle koden til List-Delete ved å introdusere sentinels. En sentinel er et dummy elemement som forenkler grensesjekkene. Vi kan f.eks introdusere et attributt L.nil i listen og bytte ut alle referanser til nil med L.nil. Attributtet L.head ville blitt byttet ut med L. nil. Pseudekoden for List-Delete ville blitt betraktelig enklere med sentinels. List-Delete(L, x) 1 x.prev.next = x.next 2 x.next.prev = x.prev
Hvordan En Lenket Liste med Sentinels Virker Figuren under viser hvordan en sirkulær dobbeltlenket liste med sentinels virker. (a) viser en tom liste. (b) viser listen etter at noen tilfeldige verdier er satt inn. (c) viser den samme listen etter at 25 er blitt satt inn. (d) viser listen etter at det elementet 1 har blitt fjernet.
Pekere og Objekter Pekere og objekter er nyttige mekanismer, men de eksisterer ikke i alle platformer. Da kan det være nødvendig å implementere de selv. Det kan også være hjelpsomt å implementere en egen utgave i miljø som støtter pekere og objekter for å økt kontrol.
Representasjon med Flere Arrays Vi kan representere objekter ved å iverate et array for hvert attributt i objektet. Et eksempel er elementet i lenkede lister. Figuren under viser hvordan tre arrays kan brukes til å holde på x.key, x.next og x.prev. Denne måten er begrenset ved at alle objekter må ha sammen antall og type attributter.
Representasjon med Én Array Alternativt kan vi representere objektene sekvensielt i et array. Dette fjerner da begrensingen med et fast antall attributter, men det blir betraktelig mer komplisert å vedlikeholde. Figuren under viser hvordan vi kan bruke et enkelt array til å lagre elementen i en lenket liste. Vi kommer til å se nærmere på det å representere objekter med flere arrays.
Allokering og Sletting av Objekter Vi kan implementer allokering og sletting av objekter i en reprentasjon med flere arrays ved å ivereta en enkeltlenket liste over ledige plasser. Vi får da et ekstra attributt free som peker til første ledige plass. Dette passer fint med lenkede lister, siden hvert objekt allerede har en next peker, men dette er på ingen måte et krav. Allocate-Object() 1 if free = = nil 2 error out of space 3 else x = free 4 free = x.next 5 return x Free-Object(x) 1 x.next = free 2 free = x
Hvordan Fungerer Allokering og Sletting? I figuren under kan du se hvordan pekere og objekter, samt ledige plasser blir iveretatt.
Representere Trær Det å representere trær er ikke så veldig annerledes enn å representere lenkede lister. Vi trenger bare flere pekere/referanser. Vi skal ta en rask titt på hvordan vi kan representere binære trær og trær med et vilkårlig antall barn.
Representere Binære Trær Vi kan enkelt representere binære trær ved å ivereta tre attributter på hver node x. x.p som peker til foreledernoden, eller til nil hvis x er rotnoden. x.left peker til venstre barn, eller til nil hvis noden er en bladnode. x.right peker til høyre barn, eller til nil hvis noden er en bladnode.
Representere Trær Vi kan utvide skjemaet fra forrige slide slik at vi kan ha et vilkårlig antall barn mindre enn k på hver node. En bedre tilnærming er left-child, right-sibling representasjon. Her vil hver node bare ha en peker til venstre barn og første nabo til høyre.