Løsningsforslag for Obligatorisk Oppgave 3 Algoritmer og Datastrukturer ITF20006 Lars Vidar Magnusson Frist 28.03.14 Den tredje obligatoriske oppgaven tar for seg forelesning 9 til 13, som dreier seg om datastrukturer. Løsningsforslaget inneholder ikke analyse av faktisk kjøretid. Dette ble gjort fordi poenget med disse var primært å la studentene få erfare forskjellen mellom faktisk kjøretid og asymptotisk kjøretid på egen hånd. Praktisk Informasjon Det er med disse oppgavene dere skal få den nødvendige erfaringen dere trenger, så det kreves at alle innleveringer skal være individuelt arbeid. Det er selvsagt til lov å hjelpe hverandre, men den endelige besvarn skal være et resultat av eget arbeid. Besvarr som viser tegn plagiering vil bli underkjente. Dere kan selv velge hvilket språk dere vil implementere algoritmene i, men hvis dere skulle trenge hjelp kan det være greit å holde seg til enten Java, C# eller C (eller SML for spesielt interesserte). Både kode og tekst skal samles i en enkel rapport som så sendes til meg på lars.v.magnusson@hiof.no. Rapporten skal være i pdf-format og skal være strukturert i henhold til oppgavene. Dere står fritt i om koden legges direkte inn i besvarsteksten som figurer eller om dere legger den til som appendix og bruker refaranser. Oppgave 1 - Stakker og Køer Denne oppgaven dreier som om stakker og køer. A - FIFO vs. LIFO Forklar forskjellen mellom FIFO og LIFO og hvordan begrepene er relatert til stakker og køer. FIFO står for First In First Out i.e. elementer som blir satt inn først kommer først ut. LIFO står for Last In First Out i.e. elementer som blir satt inn sist kommer først ut. 1
B - Applikasjonsområder Gi minst et konkret eksempel på et applikasjonsområde for både en stakk og en kø. En stakk kan anvendes til: Sjekke balanse i paranteser Sjekke gyldig XML Postfix kalkulator Dybdeførst søk... En kø kan anvendes til: Diverse køsystemer e.g. betalingskø, taxikø Enkel oppgavehåndtering Breddeførst søk... Oppgave 2 - Dictionaries Vi har i forelesningene presentert en rekke datastrukturer som implementerer dictionary grensesnittet/operasjonene Insert, Delete og Search. A - Beskriv Gi en kort beskriv av de ulike datastrukturene med fokus på kjøretiden til de ulike operasjonene. Direkteadresserte tabeller Insert - O(1). Delete - O(1). Search - O(1). Hashtabeller med chaining med dobbeltlenket liste Insert - O(1). Delete - O(1). Search - O(1 + α), α er belastningsfaktoren. Lenkede lister (dobbeltlenket) 2
Insert - O(1). Delete - O(1). Search - O(n). Binære søketrær Insert - O(h), h varierer mellom log n og n. Delete - O(h). Search - O(h). Rødsvarte søketrær Insert - O(h), h er alltid log n. Delete - O(h). Search - O(h). B-trær Insert - O(h), h er alltid log t n, t er branchfaktoren. Delete - O(h). Search - O(h). B - Applikasjonsområder Diskuter i hvilke situasjoner de ulike datastrukturene passer inn med fokus på fordelene den hver av datastrukturene tilbyr i forhold til de andre i en gitt situasjon. Ideelt sett ville vi aller heltst ta i bruk direkteadresserte tabeller siden alle operasjonene er konstante, men problemet er at dette ville kreve enorme mengder minne i mange tilfeller. Hashtabeller er et godt alternativ når antall nøkler i bruk er mye mindre enn det maksimalt mulige antallet. De gir konstant kjøretid på både innsetting og sletting, og søketiden kan til en viss grad kontrolleres ved å sørge for fornuftig belastningsfaktor. Lenkede lister er nyttige hvis man ikke vet hvor mange elementer de vil inneholde. De gir konstant innsetting og sletting uansett antall elementer i en liste, men søketiden er avhengig av størrn. De er derfor best egnet i situasjoner hvor det ikke må utføre mange søk. Binære søketrær og rødsvarte trær er anvendelige i situasjoner hvor søk er viktigere enn innsetting og sletting. Hvis vi vet at input er uniformt distribuert (eller i nærheten) er det best å ta i bruk binære søketrær siden konstantleddet 3
er minst. Hvis derimot man ikke er trygg på input distribusjonen er rødsvarte trær best, siden de garanterer en logaritmisk høyde på treet. B-trær er som binære og rødsvarte søketrær egnet for søk. De er spesielt egnet for lagring til fil siden de minimerer antall diskoperasjoner som trengs for utføre operasjonene. Oppgave 3 - Implementasjon av Søketrær I denne oppgaven skal dere implementere to av de ulike søketrærne vi har presentert i forelesningene, og dere skal sammenligne resultatet. A - Binære Søketrær Implementer binære søketrær i henhold til pseudokoden gitt i forelesningen. Det holder at dere implementerer dictionary operasjonene Insert, Delete og Search. De binære søketre operasjonene er implementert med følgende Node-klasse. class Node { int key ; Node l e f t, r i g h t, p ; public Node ( int key ) { this. key = key ; public S t r i n g t o S t r i n g ( ) { return ( key : + key + l e f t : + l e f t + r i g h t : + r i g h t + ) ; Alle operasjonene er implementert direkte utifra pseudokoden. Metodene er implementerte som statiske metoder i en java-klasse, så roten på treet holdes i en statisk variabel root. s t a t i c Node s e a r c h ( Node x, int key ) { i f ( x == null key == x. key ) return x ; i f ( key < x. key ) return s e a r c h ( x. l e f t, key ) ; return s e a r c h ( x. r i g h t, key ) ; s t a t i c void i n s e r t ( Node z ) { 4
Node y = null ; Node x = r o o t ; while ( x!= null ) { y = x ; i f ( z. key < x. key ) x = x. l e f t ; x = x. r i g h t ; z. p = y ; i f ( y == null ) r o o t = z ; i f ( z. key < y. key ) y. l e f t = z ; y. r i g h t = z ; s t a t i c void t r a n s p l a n t ( Node u, Node v ) { i f ( u. p == null ) r o o t = v ; i f ( u == u. p. l e f t ) u. p. l e f t = v ; u. p. r i g h t = v ; i f ( v!= null ) v. p = u. p ; s t a t i c Node minimum( Node x ) { while ( x. l e f t!= null ) x = x. l e f t ; return x ; s t a t i c void d e l e t e ( Node z ) { i f ( z. l e f t == null ) t r a n s p l a n t ( z, z. r i g h t ) ; i f ( z. r i g h t == null ) t r a n s p l a n t ( z, z. l e f t ) ; { Node y = minimum( z. r i g h t ) ; i f ( y. p!= z ) { t r a n s p l a n t ( y, y. r i g h t ) ; y. r i g h t = z. r i g h t ; y. r i g h t. p = y ; 5
t r a n s p l a n t ( z, y ) ; y. l e f t = z. l e f t ; y. l e f t. p = y ; B - Rød-Svarte Trær Implementer rød-svarte trær i henhold til pseudokoden gitt i forelesningen. Det holder også her at dere implementerer dictionary operasjonene Insert, Delete og Search. Rød-Svarte trær implementasjonen er basert på følgende Node-klasse. s t a t i c class Node { int key ; Node l e f t, r i g h t, p ; boolean c o l o r ; // f a l s e = s v a r t, t r u e = ørd public Node ( int key ) { this. key = key ; i f ( n i l!= null ) l e f t = r i g h t = p = n i l ; l e f t = r i g h t = p = this ; c o l o r = f a l s e ; public S t r i n g t o S t r i n g ( ) { return ( + ( c o l o r? red : black ) + key : + key + l e f t : + l e f t + r i g h t : + r i g h t + ) ; Operasjonene er også her implementerte som statiske metoder. nil refererer til en statisk node som brukes i stedet for null for å forenkle koden. Vi begynner med hjelpeoperasjonene. s t a t i c void t r a n s p l a n t ( Node u, Node v ) { i f ( u. p == n i l ) r o o t = v ; i f ( u == u. p. l e f t ) u. p. l e f t = v ; u. p. r i g h t = v ; 6
v. p = u. p ; s t a t i c Node minimum( Node x ) { while ( x. l e f t!= n i l ) x = x. l e f t ; return x ; s t a t i c void l e f t R o t a t e ( Node x ) { Node y = x. r i g h t ; x. r i g h t = y. l e f t ; i f ( y. l e f t!= n i l ) y. l e f t. p = x ; y. p = x. p ; i f ( x. p == n i l ) r o o t = y ; i f ( x == x. p. l e f t ) x. p. l e f t = y ; x. p. r i g h t = y ; y. l e f t = x ; x. p = y ; s t a t i c void r i g h t R o t a t e ( Node x ) { Node y = x. l e f t ; x. l e f t = y. r i g h t ; i f ( y. r i g h t!= n i l ) y. r i g h t. p = x ; y. p = x. p ; i f ( x. p == n i l ) r o o t = y ; i f ( x == x. p. r i g h t ) x. p. r i g h t = y ; x. p. l e f t = y ; y. r i g h t = x ; x. p = y ; Søkeoperasjonen er implementert på samme måte som i binære søketrær, bortsett fra nil. s t a t i c Node s e a r c h ( Node x, int key ) { 7
i f ( x == n i l key == x. key ) return x ; i f ( key < x. key ) return s e a r c h ( x. l e f t, key ) ; return s e a r c h ( x. r i g h t, key ) ; Insert er avhengig av en hjelpefunksjon for å rydde opp etter innsetting. Begge metodene kommer her. s t a t i c void i n s e r t F i x u p ( Node z ) { while ( z. p. c o l o r == true ) { i f ( z. p == z. p. p. l e f t ) { Node y = z. p. p. r i g h t ; i f ( y. c o l o r == true ) { z. p. c o l o r = f a l s e ; y. c o l o r = f a l s e ; z. p. p. c o l o r = true ; z = z. p. p ; { i f ( z == z. p. r i g h t ) { { z = z. p ; l e f t R o t a t e ( z ) ; z. p. c o l o r = f a l s e ; z. p. p. c o l o r = true ; r i g h t R o t a t e ( z. p. p ) ; Node y = z. p. p. l e f t ; i f ( y. c o l o r == true ) { z. p. c o l o r = f a l s e ; y. c o l o r = f a l s e ; z. p. p. c o l o r = true ; z = z. p. p ; { i f ( z == z. p. l e f t ) { z = z. p ; r i g h t R o t a t e ( z ) ; 8
z. p. c o l o r = f a l s e ; z. p. p. c o l o r = true ; l e f t R o t a t e ( z. p. p ) ; r o o t. c o l o r = f a l s e ; s t a t i c void i n s e r t ( Node z ) { Node y = n i l ; Node x = r o o t ; while ( x!= n i l ) { y = x ; i f ( z. key < x. key ) x = x. l e f t ; x = x. r i g h t ; z. p = y ; i f ( y == n i l ) r o o t = z ; i f ( z. key < y. key ) y. l e f t = z ; y. r i g h t = z ; z. l e f t = n i l ; z. r i g h t = n i l ; z. c o l o r = true ; i n s e r t F i x u p ( z ) ; Delete er også avhengig av en hjelpefunksjon for å rydde. s t a t i c void d e l e t e F i x u p ( Node x ) { while ( x!= r o o t && x. c o l o r == f a l s e ) { i f ( x == x. p. l e f t ) { Node w = x. p. r i g h t ; i f (w. c o l o r == true ) { w. c o l o r = f a l s e ; x. p. c o l o r = true ; l e f t R o t a t e ( x. p ) ; w = x. p. r i g h t ; 9
i f (w. l e f t. c o l o r == f a l s e && w. r i g h t. c o l o r == f a l s e ) { w. c o l o r = true ; x = x. p ; { i f (w. r i g h t. c o l o r == f a l s e ) { { w. l e f t. c o l o r = f a l s e ; w. c o l o r = true ; r i g h t R o t a t e (w) ; w = x. p. r i g h t ; w. c o l o r = x. p. c o l o r ; x. p. c o l o r = f a l s e ; w. r i g h t. c o l o r = f a l s e ; l e f t R o t a t e ( x. p ) ; x = r o o t ; Node w = x. p. l e f t ; i f (w. c o l o r == true ) { w. c o l o r = f a l s e ; x. p. c o l o r = true ; r i g h t R o t a t e ( x. p ) ; w = x. p. l e f t ; i f (w. r i g h t. c o l o r == f a l s e && w. l e f t. c o l o r == f a l s e ) { w. c o l o r = true ; x = x. p ; { i f (w. l e f t. c o l o r == f a l s e ) { w. r i g h t. c o l o r = f a l s e ; w. c o l o r = true ; l e f t R o t a t e (w) ; w = x. p. l e f t ; w. c o l o r = x. p. c o l o r ; x. p. c o l o r = f a l s e ; w. l e f t. c o l o r = f a l s e ; 10
r i g h t R o t a t e ( x. p ) ; x = r o o t ; x. c o l o r = f a l s e ; s t a t i c void d e l e t e ( Node z ) { Node y = z ; Node x = n i l ; boolean y O r i g i n a l C o l o r = y. c o l o r ; i f ( z. l e f t == n i l ) { x = z. r i g h t ; t r a n s p l a n t ( z, z. r i g h t ) ; i f ( z. r i g h t == n i l ) { x = z. l e f t ; t r a n s p l a n t ( z, z. l e f t ) ; { y = minimum( z. r i g h t ) ; y O r i g i n a l C o l o r = y. c o l o r ; x = y. r i g h t ; i f ( y. p == z ) x. p = y ; { t r a n s p l a n t ( y, y. r i g h t ) ; y. r i g h t = z. r i g h t ; y. r i g h t. p = y ; t r a n s p l a n t ( z, y ) ; y. l e f t = z. l e f t ; y. l e f t. p = y ; y. c o l o r = z. c o l o r ; i f ( y O r i g i n a l C o l o r == f a l s e ) d e l e t e F i x u p ( x ) ; 11
C - Sammenligning I denne oppgaven skal implementasjonene testes opp i mot hverandre. Den faktiske kjøretiden for de ulike operasjonene skal måles for ulike input og inputstørrr. Resultatene skal presenteres i en tabell og/eller en graf, og de skal diskuteres opp i mot kjøretidsanalysen gitt i forelesningene. Lykke til! 12