INF-2810 V 2012 Oppgavesett 10, kalenderuke 12. Oppgave 1 Minimum edit distance Vi vil finne det minste antall redigeringsoperasjoner som kreves for å komme fra strengen A til strengen B. Strengene oppgis som symboler og konverteres til strenger som igjen konverteres til lister med tegn. (string->list (symbol->string 'hallo)) ==> (#\h #\a #\l #\l #\o). For sammenligning av listelementer bruker vi primitiven char=?. 1.a Prosedyren min-edit-dist-functional finner det minste antall redigeringsoperasjoner ved å løpe gjennom de to listene i en rekursive prosedyre. - Hvis A tar slutt før B økes kostnaden med lengden til resten av B, og vice versa. - Så lenge car-elementene i de to listen er like, går vi videre i begge uten at kostnaden øker. - Når car-elementene er ulike, fortsetter rekursjonen i to grener - til venstre med (cdr A) og B og - til høyre med A og (cdr B) og returnerer omkostningene ved den billigste av de to grenene. For hver forgrening økes kostnaden med 1. Eks: (forskjell '(h a l l o) '(h a l lo)) ==> 0 (forskjell '(h a l l o) '(h a l o)) ==> 1 (forskjell '(h a l l o) '(h o l a)) ==> 5 (forskjell '(h a l l o) '(t y t t e b æ r)) ==> 13 Implementer min-edit-dist-functional. 1.b Prosedyre n min-edit-dist-destructive finner det minste antall redigeringsoperasjoner ved hjelp av en matrise (todimensjonal tabell) M der liste A bestemmer radene og liste B bestemmmer kolonnene. La a-leng og b-leng være lengdene til henholdsvis A og B. Da skal første rad i M være en enumerering fra 0 tilb-leng. Under dettte skal det være a-leng rader som starter med henholdsvis 1, 2, osv. og som ellers inneholder nuller. 1
F.eks. hvis A = (h a l l o) og B = (h o l a), får vi (0 1 2 3 4) (1 0 0 0 0) M = (2 0 0 0 0) (3 0 0 0 0) (4 0 0 0 0) (5 0 0 0 0) som vi for leselighetens skyld skriver slik: 0 1 2 3 4 1 0 0 0 0 h 2 0 0 0 0 a 3 0 0 0 0 l 4 0 0 0 0 l 5 0 0 0 0 o h o l a Den første radene er indeksene til tegnene i B, regnet fra 1, og gir samtidig kostnadene ved å settte inn hele B. Den første kolonnen er indeksene til tegnene i A, regnet fra 1, og gir samtidig kostnadene ved å slettte hele A. Der neste tegn i A = neste tegn i B, kan vi gå diagonalt uten omkostninger. Algoritmen løper gjennom matrisen og fyller cellene med kostnadene ved å komme dit. Utfylling av kostnadene i de enkelte cellene Står vi i celle M[i, j], så er - kostnaden ved å gå ett trinn bort, til M[i + 1, j], eller ett trinn ned, til M[i, j + 1] = 1, mens - kostnadene ved å gå diagonalt, til M[i + 1, j + 1] er enten 0 eller 2, avhengig av om B[i + 1] = A[j + 1] eller ikke. Disse ett-trinns-kostnadene legges til det det har kostet å komme til M[i, j]. Med eksemplet over får vi, når hele matrisen en er gjennomløpt, 0 1 2 3 4 1 0 1 2 3 h 2 1 2 3 4 a 3 2 3 2 3 l 4 3 4 3 4 l 5 4 3 4 5 o h o l a De røde tallene viser én mulig vei, mens de fete tallene viser det området den korteste veien må gå gjennom. Fyll ut prosedyren traverse i skallet min-edit-distance-skall.scm. Her er en nokså ordrik gjennomgang av minimum edit distance algoritmen: http://folk.uio.no/asbr/lecturesandcompendia/mineddist.pdf 2
Oppgave 2 Antall binære trær med n noder Med null noder får vi ett tre det tomme treet Med én node får vi også ett tre. Med to noder får vi to trær Med tre noder får vi fem trær Med fire node får vi fjorten trær I mengden av trær med nodene {1, 2, 3, 4, 5} har alle én av disse nodene som rot. I mengden av trær der 1 er rot har vi ett venstre-subtre,, og 14 høyre-subtrær, tilsammen 14 trær. Med 2 som rot har vi 1 venstre- og 5 høyre-subtrær, tilsammen 5 trær. Med 3 som rot har vi 2 venstre og 2 høyre-subtrær som, når vi kombinerer dem, gir tilsammen 2 2 = 4 trær. Med rot 4 og 5 har vi, som med rot 2 og 1, hhv. 5 og 14 trær, og legger vi alt dette sammen får vi 42 trær. Tilsvarende, med nodene 1.. 6, får vi 1 42 + 1 14 + 2 5 + 5 2 + 14 1 + 42 1 = 132 trær. Generelt, med n noder og rot k, 1 k n er antall venstre-subtrær = antall trær med k 1 noder, antall høyre-subtrær = antall trær med n k noder, og antall trær totalt = produktet av disse. Tilsammen er antall trær med n noder = summen av produktene av antall trær med i 1 noder og antall trær med n i noder, for i = 1 til n. Eller med andre ord, hvis f(n) = antall trær med n noder, har vi: n f(n) = f(0) f(n 1) +.. + f(n 1)f(0) = f(i 1)f(n i), n 1 (1) i=1 3
2a. Algoritme 1 Med henblikk på en implementasjon kan vi bruke en lokal iterator for akkumulering av summetermen f(i 1)f(n i). (define (count-b-trees-1 n) (2) (define (sum i s) <tell fra og med i til og med n og akkumulér produktene i summen s>) <kroppen til count-b-trees-1>) Skriv count-b-trees i hht. algoritme 1. 2b. Algoritme 2 Selv om vi har en lokal akkumulerende iterator i (2) får vi allikevel eksponensiell vekst, i og med at vi hele tiden tar produktet av to kall på count-b-trees. For å unngå dette kan vi lage en liste med de resultatene vi har funnet i stigende orden (r 1,, r k ). Da vil r k+1 være r 1 r k + r 2 r k 1 + + r k r 1. (3) F.eks. de 6 første resultatene, fra f(0) til f(5), er 1, 1, 2, 5, 14, 42. Dette gir 1 1 2 5 14 42 1 1 2 5 14 42 132 f(6) = + + + + + f(7) = + + + + + + (4) 42 14 5 2 1 1 132 42 14 5 2 1 1 = 42 + 14 + 10 + 10 + 14 + 42 = 132 = 132 + 42 + 28 + 25 + 28 + 42 + 132 = 429 Denne algoritmen gir kvadratisk vekst. Skriv count-b-trees-2 i hht. algoritme 2. 2c. Algoritme 3 Som vi ser gir beregningen i implementasjon 2 en symmetrisk liste med produkter. Dette kan vi utnytte slik: Hvis vi har et like antall summer dobler vi resultatet av summeringen av den første halvdelen av produktene, og hvis vi har et odde antall summer legger vi kvadratet av det midterste tallet til det dobbelte av summen opp til midten. F.eks. slik: (1 42 + 1 14 + 2 5 + 5 2 + 14 1 + 42 1) = 2(1 42 + 1 14 + 2 5) (1 132 + 1 42 + 2 14 + 5 5 + 14 2 + 42 1 + 132 1) = 2(1 132 + 1 42 + 2 14) + 5 2. (5) Skriv count-b-trees-3 i hht. algoritme 3. 4
2d. Algoritme 4 Vi antar nå at det finnes en lineær funksjon g slik at I så fall skal vi ha f(n) = g(n)f(n 1). (6) For n = 10 får vi g(1), g(2),, g(n 1) = f(2) / f (1), f(3) / f (2),, f(n) / f (n 1), 1 4 1 1 1 2 5 1, 2, 2, 2, 3, 3, 3, 3, 3, 3 (7) 2 5 7 4 3 5 11 1 2 5 14 3 22 13 10 17 38 =,,,,,,,,, (8) 1 1 2 5 1 7 4 3 5 11 Etter våre antagerlser skal det være mulig å skrive om (8) slik at rekkene med tellere og nevnere blir jevnt stigende. Vi kan gjette, eller vi kan skrive en prosedyre som gir oss slike rekker, om input passer. - Vi løper gjennom listen med tall og ser på tre suksessive tall av gangen, a, b og c og - beregner d til taket av snittet av a og c, (a + b)/2, i Scheme: (inexact->exact (ceiling (/ (+ a c) 2))). - Hvis b d legger vi d inn i ut-listen, og, for å få rett gjennomsnitt i neste runde, legger vi også d først i resten av in-listen; og så noterer vi oss at listen ble endret. Prosessen gjentas inntil ingen endringer ble utført. Feks. skal vi for rekken av tellerne i (8) få (lift '(1 2 5 14 3 22 13 10 17 38)) ==> (1 6 10 14 18 22 26 30 34) Vi ser at fra og med det andre tallet stiger rekken jevnt med 4. Skriv prosedyren lift for løfting og utjevning av lister med tall, bruk prosedyren til å bestemme funksjonen g, og skriv count-b-trees som en implementasjon av (6). Drøfting av prosedyren lift La P = (p 1,, p n ) og Q = (q 1,, q n ) være lister med heltall slik at P er jevnt stigende, Q P, q 1 = p 1, q n = p n, og q i p i, for 1 i n, (P og Q begynner og slutter med samme tall, og ellers er tallene i Q de tilsvarende tallene i P). Da er lift(q) = P. - At P er jevnt stigende betyr at for alle tripler xyz i P så er y = (x + z)/2. - Hvis Q P så finnes det en trippel xyz i Q slik at y < (x + z)/2, fordi siden P er stigende må q n være større enn q 1, Q må ha et minimum m < q n, og hvis q m er det siste tallet i Q som er lik m, må q m < (q m 1 + q m+1 )/2. Etter en runde i den indre iteratoren vil q m = (q m 1 + q m+1 )/2. Da er enten Q = P eller så gjelder det samme som over. 5
2e. Telling av antall beregninger For å telle antall kjerneoperasjoner under utførelsen av de ulike implementasjonene kan vi erstatte kallene på * med en multiplikator med en intern kall-teller. Multiplikatoren må være en prosedyre som har en lokal teller, initielt = 0, i sin umiddelbare omgivelse, tar et valgritt antall argumenter og virker slik: Hvis den kalles uten argument, setter den telleren til 0 og returnerer den verdien telleren hadde før nullstillingen, og ellers øker den telleren med 1 og returnerer produktet av sine argumenter. Skriv ovennevnte konstruktor, bruk denne til å lage en prosedyren mul, erstatt alle kall på * med kall på mul, og kall mul med 0 som argument etter hvert kall på de ulike implementasjonene. 2f. For spesielt interesserte En implementasjon av algoritme 4 viser at f(n) 2(2n 3) g(n) = = (9) f(n 1) n og dermed at 2(2n 3) f(n 1) f(1) = 1, f(n) =, n > 1 (10) n som vi kan skrive om til: 2(2 2 3) 2(2 3 3)... 2(2 n 3) 2 n 1 1 3... (2n 3) f(n) = = osv. (11) 2 3... n n! til vi står igjen med et relativt enkelt uttrykk på fomen n( (n))! = (12) (n!) De som har moro av det, kan prøve å gjennomføre omskriving, og finne og, og/eller finne andre omskrivninger som gir andre forenklinger. 6