Presentasjon lastes. Vennligst vent

Presentasjon lastes. Vennligst vent

Kap 07 Stakk I dette kapitlet skal vi se på datastrukturen stakk.

Liknende presentasjoner


Presentasjon om: "Kap 07 Stakk I dette kapitlet skal vi se på datastrukturen stakk."— Utskrift av presentasjonen:

1 Kap 07 Stakk I dette kapitlet skal vi se på datastrukturen stakk.

2 Stakk - Definisjon En stakk er en lineær struktur
hvor elementer kan innsetttes / fjernes kun i den ene enden av listen, kalt toppen av stakken. Stakk: LIFO (Last In First Out). I kapitlene 04 og 06 har vi sett på såkalte lister. I disse listene tillot vi innsetting og sletting av poster hvor som helst i listen. Det finnes relativt mange situasjoner i databehandling hvor man ønsker å sette restriksjoner angående innsetting / sletting av poster slik at disse kan forekomme kun i begynnelsen eller slutten av listen. To av data-strukturene som er nyttige i slike tilfeller er STAKK og KØ (eng. STACK and QUEUE). En stakk er en lineær struktur hvor poster kan innsettes / fjernes kun i den ene enden av listen. Figuren nedenfor viser et eksempel fra dagliglivet på en slik struktur: En stakk av tallerkener. Legg merke til at poster (elementer) kan legges på / fjernes fra kun toppen av stakken. Spesielt betyr dette at den første posten som skal fjernes fra stakken er den siste som er addert til stakken. Av denne grunn kalles en stakk for LIFO (Last In First Out). Vi har nå følgende definisjon på en stakk: EN STAKK ER EN LISTE-STRUKTUR HVOR INNSETTING / FJERNING AV ELEMENTER ER TILLATT KUN I DEN ENE ENDEN, KALT TOPPEN AV STAKKEN.    Følgende to basis-operasjoner er knyttet til stakk: PUSH Innsetting av element i en stakk POP Fjerning av element fra en stakk To basis-operasjoner knyttet til stakk: push Innsetting av element i en stakk pop Fjerning av element fra en stakk

3 Anvendelser Prosedyrer / funksjoner Rekursjon
Beregning av aritmetiske uttrykk Sortering Backtracking Diagnostisering Labyrinttraversering ... Selv om en stakk ser ut til å være en datastruktur med meget store restriksjoner, er den svært utbredt innen EDB. Vi skal seinere i kapitlet se nærmere på praktisk anvendelse av stakk, og her kun nevne stikkord ord som: Prosedyrer / Funksjoner Rekursjon Beregning av aritmetiske uttrykk Sortering Backtracking Diagnostisering Labyrinttraversering

4 Innsetting i en stakk Plassering av elementene A, B, C, D, E, F i nevnte rekkefølge i en stakk. La oss tenke oss at vi plasserer postene A,B,C,D,E og F i nevnte rekkefølge i en stakk. Figuren ovenfor til venstre viser situasjonen. Stakken kan imidlertd like gjerne beskrives vha en av de tre figurene til høyre.

5 Innsetting / Uttak 1. push A 2. push B 3. push C 4. pop (C) 5. pop (B)
6. push D 7. push E 8. pop (E) La oss tenke oss at vi gjør følgende operasjoner på en stakk som opprinnelig er tom: 1) PUSH A 2) PUSH B 3) PUSH C 4) POP (C) 5) POP (B) 6) PUSH D 7) PUSH E 8) POP (E) Figuren ovenfor illustrerer situasjonen.

6 Prosedyrer / Funksjoner
Vi skal kort skjematisere hvordan en datamaskin benytter en stakk ved bruk av prosedyrer / funksjoner i et applikasjons-program. Vi tenker oss en program-struktur skissert ved hipogrammet nedenfor (H står for hoved-progam og P står for delprogram (prosedyre)): Hver gang en nytt delprogram bli kalt opp, må opplysninger om del-programmet som midlertidig forlates lagres, slik at det er mulig å vende tilbake til korrekt situasjon når det nye delprogrammet er ferdigbehandlet. Til lagring av slike opplysninger benyttes ofte en stakk. Det er ingenting i veien for at et del-program kan kalle opp seg selv. Vi får isåfall det vi kaller rekursjon. Flere høgnivåspråk (bl.a. Pascal og C) inneholder slike muligheter for rekursiv programmering.  I figuren til høyre vises hvorledes stakk-opplysninger lagres ved hvert nytt prosedyre-kall.

7 push - Array-implementering
post push Array-implementering top x x x push (stakk,maxStakk,post,top,full) /* Rutinen plasserer en post (et element) i en stakk */ /* stack : stakken som posten skal plasseres i */ /* maxStakk : Maksimalt antall elementer i stakken */ /* post : Posten som skal plasseres i stakken */ /* top : Lokasjonen til topp-elementet i stakken */ /* full : Returnerer med vedien true */ /* hvis stakken er full */ /* slik at post ikke kan plasseres */ IF top = maxStack THEN full := true ELSE full := false top := top + 1 stakk[top] := post ENDIF Rutinen push plasserer en post i en stakk. En stakk kan i datamaskinen representeres på ulike vis, f.eks ved en lineær array eller vha en enveis-liste. Vi skal i dette avsnittet se litt nærmere på bruk av array til representasjon av en stakk. Vi innfører følgende to variable: top Viser lokasjonen til topp-elementet i stakken. top settes lik 0 hvis stakken er tom. maxStakk Maksimalt antall elementer som det er plass til i stakken.

8 pop - Array-implementering
post top x x x pop (stakk,post,top,tom) /* Rutinen returnerer og sletter en post i en stakk */ /* stakk : stakken som posten skal plasseres i */ /* post : Posten som skal returneres fra stakken */ /* top : Lokasjonen til topp-elementet i stakken */ /* tom : Returnerer med vedien true */ /* hvis stakken er tom */ /* slik at post ikke kan returneres */ IF top = 0 THEN tom := true ELSE tom := false post := stakk[top] top := top - 1 ENDIF Rutinen pop returnerer en post i en stakk. En stakk kan i datamaskinen representeres på ulike vis, f.eks ved en lineær array eller vha en enveis-liste. Vi skal i dette avsnittet se litt nærmere på bruk av array til representasjon av en stakk. top Viser lokasjonen til topp-elementet i stakken. top settes lik 0 hvis stakken er tom.

9 push - Lenket liste implementering
top stakkNode x post 4 3 data neste 2 1 p push (post,top) /* Rutinen plasserer en post i en stakk med dummy-element. */ /* post : Posten som skal plasseres i stakken */ /* top : Peker til toppen (dummy) i stakken */ p := new stakkNode p.data := post p.neste := top.neste top.neste := p push Lenket liste implementering (åpen forlengs liste med dummy-element). 1 2 3 4

10 pop - Lenket liste implementering
top post stakkNode 2 3 data neste x p 1 pop (post,top) /* Rutinen returnerer og sletter en post i en stakk . */ /* post : Posten som skal returneres fra stakken */ /* top : Peker til toppen (dummy) i stakken */ IF top.neste = null tom := true ELSE tom := false p := top.neste post := p.data top.neste := p.neste delete(p) ENDIF pop Lenket liste implementering (åpen forlengs liste med dummy-element). 1 2 3

11 Beregning av aritmetisk uttrykk (1/3)
a + b * c 2 + 3 * 4 = = 14 Korrekt 2 + 3 * 4 = 5 * 4 = Feil * har høyere prioritet enn + 3 skal multipliseres med 4 før dette svaret adderes til 2 Problemer hvor bruk av stakk er hensiktsmessig involverer ofte nødvendigheten av såkalt backtracking for å returnere til en foregående tilstand. Som et nærliggende eksempel kan vi tenke oss at vi skal forsøke å finne veien ut av en labyrint. En metode er å forsøke å følge en vilkårlig vei så langt som mulig. Hvis vi kommer til en blindvei, er det nødvendig med 'backtracking' ved at vi returnerer til de forgående lokasjonene i motsatt rekkefølge. Et analogt problem oppstår i forbindelse med utvikling av en kompilator (et program som oversetter fra høgnivå-språk til maskin-språk) ved beregning av aritmetiske uttrykk. La oss anta at vi skal beregne verdien av uttrykket: a + b * c Når kompilatoren scanner igjennom uttrykket fra venstre mot høyre, kan ikke beregninger utføres hver gang en operator påtreffes. Resultatet blir galt hvis følgende utføres: 1) a leses 2) + leses 3) b leses 4) a + b beregnes 5) * leses 6) c leses 7) verdien av a+b multipliseres med c Feilen består i at * har høyere prioritet enn +. Altså skal multiplikasjonen b*c utføres før operasjonen med +. Ved scanning av uttrykket ovenfor påtreffer operatoren +, må altså først videre scanning utføres for å undersøke om det finnes operatorer med høyere prioritet. Deretter må backtracking utføres for å gå tilbake til tidligere operatorer.

12 Beregning av aritmetisk uttrykk (2/3)
Operator a + b * c Når vi ved scanning av uttrykket ovenfor påtreffer operatoren +, må altså først videre scanning utføres for å undersøke om det finnes operatorer med høyere prioritet. Deretter må backtracking utføres for å gå tilbake til tidligere operatorer. Hvis vi leser det aritmetiske uttrykket fra venstre mot høyre, kan vi ikke utføre operasjonene etter hvert som vi treffer på en operator. Vi må lese videre for å se om andre operatorer har høyere prioritet.

13 Beregning av aritmetisk uttrykk (3/3)
Operator Et aritmetisk uttrykk skrevet på denne måten, med en operand på hver side av tilhørende operator, sies å være skrevet på infix-form. a + b * c Operand Uttrykk av typen a + b * c kalles INFIX fordi operatorene + og * finnes inne i utrykket. Det har vist seg at beregning av slike aritmetiske uttrykk kan forenkles betraktelig ved å plassere operatorene i en slags prioritert rekkefølge 'foran' (PREFIX) eller 'bak' (POSTFIX) uttrykket. Ved bruk av PREFIX / POSTFIX notasjon unngår vi også bruk av parenteser. Det er mulig å omskrive infix-uttrykket til en ny form slik at en operasjon kan utføres umiddelbart hver gang en operator påtreffes når uttrykket leses fra venstre mot høyre. To slike formes finnes: prefix og postfix.

14 Prefix - Infix - Postfix (1/2)
a + b Prefix En operator står foran tilhørende to operander. Hver gang en operator påtreffes, utføres operasjonen på de to etterfølgende operandene. + a b Infix En operator står mellom tilhørende to operander. Først paranterisering, så utføring av operasjon på operandene på hver side. a + b Prefix, Infix og Postfix form for det aritmetiske uttrykket a+b. Postfix En operator står bak tilhørende to operander. Hver gang en operator påtreffes, utføres operasjonen på de to foregående operandene. a b +

15 Prefix - Infix - Postfix (2/2)
a + b * c Prefix En operator står foran tilhørende to operander. Hver gang en operator påtreffes, utføres operasjonen på de to etterfølgende operandene. + a * b c Infix En operator står mellom tilhørende to operander. Først paranterisering, så utføring av operasjon på operandene på hver side. a + b * c Prefix, Infix og Postfix form for det aritmetiske uttrykket a+b*c. Postfix En operator står bak tilhørende to operander. Hver gang en operator påtreffes, utføres operasjonen på de to foregående operandene. a b c * +

16 Infix - Postfix Infix En operator står mellom tilhørende to operander.
a + b * c Infix En operator står mellom tilhørende to operander. Først paranterisering, så utføring av operasjon på operandene på hver side. a + b * c 2 + 3 * 4 = 2 + (3 * 4) = 2 + 12 = 14 Postfix En operator står bak tilhørende to operander. Hver gang en operator påtreffes, utføres operasjonen på de to foregående operandene. a b c * + Beregning av infix-uttrykket a+b*c = 2+3*4 og tilhørende postfixuttrykk abc*+ 2 3 4 * + = 2 12 + = 14

17 Postfix - Eksempel a + b * c a b c * + 2 + 3 * 4 = 14 2 3 4 * + = 14
2 3 4 * + = 14 2 (operand) leses 2 3 4 * + 3 (operand) leses 2 3 4 * + 4 (operand) leses 2 3 4 * + * (operator) leses * virker på de to siste operandene 3 og 4 og gir 3 * 4 = 12 2 3 4 * + Beregning av infix-uttrykket a+b*c utfra tilhørende postfix-uttrykk abc*+ + (operator) leses + virker på de to siste operandene 2 og 12 og gir = 14 14

18 Omforming fra infix til prefix
a + b * c 0. Infix-form 1. Komplett parenterisering 2. Flytt hver operator til plassen like bak tilhørende venstre-parentes 3. Fjern alle parenteser ( a + (b * c) ) Manuell omforming av infix-uttrykket a+b*c til tilhørende prefix-uttrykk +a*bc. 0. Uttrykket på infix-form a+b*c. 1. Komplett parenterisering. Her settes på et parentespar for hver operator i infix-uttrykket. Med 2 operatorer (+ og *) i vårt eksempel, får vi 2 parentespar, til sammen 4 parenteser (2 høyre og 2 venstre). 2. Flytt hver operator til plassen like bak tilhørende venstre-parentes. 3. Fjern alle parenteser. Tilbake står nå prefix-uttrykket +a*bc. ( + a (* b c ) ) + a * b c

19 Omforming fra infix til postfix
a + b * c 0. Infix-form 1. Komplett parenterisering 2. Flytt hver operator til plassen like foran tilhørende høyre-parentes 3. Fjern alle parenteser ( a + (b * c) ) Manuell omforming av infix-uttrykket a+b*c til tilhørende postfix-uttrykk abc*+. 0. Uttrykket på infix-form a+b*c. 1. Komplett parenterisering. Her settes på et parentespar for hver operator i infix-uttrykket. Med 2 operatorer (+ og *) i vårt eksempel, får vi 2 parentespar, til sammen 4 parenteser (2 høyre og 2 venstre). 2. Flytt hver operator til plassen like foran tilhørende høyre-parentes. 3. Fjern alle parenteser. Tilbake står nå postfix-uttrykket abc*+. ( a (b c *) + ) a b c * +

20 Infix --> Postfix Parenterisering
a / b ^ c + d * e - a * c Infix --> Postfix Parenterisering a b c ^ / d e * + a c * - a / b ^ c d * e a * c ( ( ( a / ( b ^ c ) ) + ( d * e ) ) - ( a * c ) ) ( ( ( a ( b c ^ ) / ) ( d e * ) + ) ( a c * ) - ) a b c ^ / d e * a c * - Infix - + / ^ * * Parenterisering Manuell omforming av infix-uttrykket a/b^c+d*e-a*c til tilhørende postfix-uttrykk abc^/de*+ac*-. 0. Uttrykket på infix-form a/b^c+d*e-a*. 1. Komplett parenterisering. Her settes på et parentespar for hver operator i infix-uttrykket. Med 6 operatorer i vårt eksempel, får vi 6 parentespar, til sammen 12 parenteser (2 høyre og 2 venstre). 2. Flytt hver operator til plassen like foran tilhørende høyre-parentes. 3. Fjern alle parenteser. Tilbake står nå postfix-uttrykket abc^/de*+ac*-. Det er enklere for en datamaskin og beregne verdien av POSTFIX-uttrykkket siden operatorene her er plassert i prioritert rekkefølge slik at backtracking-problemet blir enklere. Straks data-programmet påtreffer en operator, utføres denne operasjonen på de to foregående operandene. I beregning av uttrykket abc^/de*+ac*- gjøres derfor følgende: 1) a leses 2) b leses 3) c leses 4) ^ leses 5) beregn op1 := b^c 6) / leses 7) beregn op2 := a/op1 8) d leses 9) e leses 10) * leses 11) beregn op3 := d*e 12) + leses 13) beregn op4 := op2 + op3 14) a leses 15) c leses 16) * leses 17) beregn op5 := a*c 18) - leses 19) beregn svar:= op4 + op5 Operatorflytting Postfix

21 Infix --> Postfix --> Beregnet verdi
Komplett parenterisering er en tungvint prosess spesielt mht maskinell behandling. Vi benytter følgende to-delte strategi: 1. Utform en algoritme hvor input er et infix-uttrykk og hvor output er et postfix- (eller prefix-) uttrykk. 2. Utform en algoritme som scanner gjennom postfix- (eller prefix-) uttrykket og beregner verdien. Parenterisering har flere ulemper: 1) Vi ønsker ikke å lese inn (taste inn) komplette parenteriserte uttrykk. 2) Det er tungvint å lage et data-program som parenteriserer. Vi forsøker nå å løse problemet med beregning av aritmetiske uttrykk ved følgende step: 1) Utform en algoritme hvor INPUT er et INFIX-uttrykk og hvor OUTPUT er et POSTFIX- (eller PREFIX-) uttrykk. 2) Utform en algoritme som scanner igjennom POSTFIX- (eller PREFIX-) uttrykket og beregner verdien.

22 Jernbane-algoritme - Ombytting
Omforming mellom prefix, infix og postfix innebærer endring av rekkefølgen ved at operatorer flyttes. Til dette benytter vi en såkalt jernbane-algoritme (navnet kommer av at ombyttingen er analog med ombytting av vogner i et togsett). Togsett hvor to av vognene (3 og 4) er byttet om Opprinnelig togsett (1 er lokomotivet) Til omgjøring av et infix-uttrykk til postfix-form, benytter vi en såkalt jernbane-algoritme. Prinsippet er det samme som ved skifting av rekkefølgen av to eller flere jernbanevogner i et togsett, derav navnet jernbane-algoritmen. 5 3 4 2 1 5 4 3 2 1 Disse to skal byttes om

23 Jernbane-algoritmen - Eks ombytting
5 4 3 2 1 5 3 2 1 1. Opprinnelig togsett 4. Vogn 3 hektes av ved vogn 5 4 5 4 3 2 1 5 3 4 2 1 2. Vogn 5 hektes av 5. Vogn 4 hentes fra sidespor Eks på jernbane-algoritmen. Et vogntog består av 5 vogner (vognene ). Vi ønsker å skifte rekkefølgen på vogn nr 3 og vogn nr 4, dvs vi ønsker togsettet Dette løser vi vha et sidespor. 1. Først hekter vi av vogn 5 til venstre. 2. Deretter kjører vi frem igjen til høyre med det resterende vognsettet 3. Vi hekter av vogn nr 4 på et sidespor og kjører vognsettet tilbake. 4. Vi hekter av vogn nr 3 rett foran vogn nr 5 (og kobler disse sammen) og kjører tilbake med vognsettet 2-1. 5. Vi kjører ned på sidesporet og henter vogn nr 4, dvs vi returnerer med vognsettet 6. Vi kjører vognsettet til venstre og hekter vogn nr 4 til vogn nr 3. Vi har nå vognsettet , dvs vi har byttet rekkefølgen på vogn 3 og 4. 5 3 2 1 5 3 4 2 1 3. Vogn 4 plasseres på sidespor 6. Resterende tog (4, 2 og 1) kobles til vognene 5 og 3 4

24 Jernbane-algoritmen - Figur
Postfix Streng som inneholder det aritmetiske uttrykket i postfix-notasjon Infix Streng som inneholder det aritmetiske uttrykket i infix-notasjon a b c ^ / d e * + a c * - a / b ^ c + d * e - a * c ^ / # Vi skal nå ta for oss jernbane-algoritmen til bruk for omgjøring av et aritmetisk uttrykk fra infix-form til postfix-form. På figuren ovenfor vises infix-uttrykket til høyre. Vi leses infix-uttrykket fra venstre mot høyre. For hver operand vi leser, flytter vi dette over til postfix-uttrykket til venstre. Hver gang vi leser en operator, sammenligner vi denne operatoren med den operatoren som ligger øverst opertor-stakken OpStakk. Hvis operator fra OpStakk har høyere prioritet, flyttes denne over til postfix-uttrykket og ny operator poppes fra OpStakk. Hvis operator fra OStakk ikke har høyere prioritet, pushes denne operatoren ned på stakken igjen og lest operator fra infix-uttrykket pushes etter opp på stakken. Ved input av infix (f.eks. inntasting fra tastaturet) passer vi på at strengen avsluttes med #. Tilsvarende vil programmet sørge for at postfix kommer ut med en streng som er avsluttet med det samme tegnet #. OpStakk Array-stakk som kan inneholde aritmetiske operatorer * / ^ venstreparenteser ( spesielt avslutningstegn #

25 Infixprioritet - Stakkprioritet
- Alle operatorer (inkludert parentes) får tildelt en prioritetsverdi. - Operander lest fra infix-uttrykket konkateneres til postfix-uttrykket. - Ved lesing av operator fra infix-uttrykket, sammenlignes denne operatoren med operatoren øverst på operator-stakken. Hvis infix-operatoren har høyere prioritet, pushes denne ned på stakken, hvis ikke konkateneres stakk-operatoren til postfix-uttrykket og neste stakk-operator poppes. Vi definerer følgende to funksjoner: InfixPrioritet og StakkPrioritet Begge disse funksjonene har operatorer, parenteser eller avslutnings-tegnet # som input og returnerer et heltall gitt ved følgende tabell:

26 Operator slutt = # vParentes = ( hParentes = )
uOperator = operator + vParentes Vi skal nå se på en algoritme/pseudokode for omforming fra infix til postfix. Vi trenger først noen operator-notasjoner som vist ovenfor.

27 infix_postfix (infix, postfix)
/* Rutinen omformer fra infix til postfix */ ... REPEAT Les(infix,si) // les si IF si IN uOperator THEN // uOperator pop(opStakk,so) WHILE stakkPrioritet(so) >= infixPrioritet(si) concat(postfix,so) ENDWHILE push(opStakk,so) push(opStakk,si) ELSEIF si = hParentes THEN // hParentes WHILE so != vParentes ELSEIF si = slutt // slutt WHILE NOT tom ELSE // operand concat(postfix,si) ENDIF UNTIL si = slutt J e r n b a n e a l g o r i t m e n Vi skal nå se på en algoritme/pseudokode for omforming fra infix til postfix. Videre definerer vi følgende prosedyrer: Les (Infix,Si) : Leser neste operand / operator si fra infix. Concat (Postfix,Op) : Adderer operand / operator op til slutten av strengen postfix.

28 Infix --> Postfix - Eksempel
Lest tegn Postfix Stakk Infix # a/b^c+d*e-a*c# a a # /b^c+d*e-a*c# / a /# b^c+d*e-a*c# b ab /# ^c+d*e-a*c# ^ ab ^/# c+d*e-a*c# c abc ^/# +d*e-a*c# + abc^/ # d*e-a*c# d abc^/d # *e-a*c# * abc^/d *+# e-a*c# e abc^/de *+# -a*c# - abc^/de* # a*c# a abc^/de*+a # *c# * abc^/de*+a *-# c# c abc^/de*+ac *-# # # abc^/de*+ac*-# La oss nå i detalj se hva som skjer med infix-uttrykket a/b^c+d*e-a*c ved bruk av den foregående algoritmen. I skjemaet vises til venstre hvilken del av infix-uttrykket som til enhver tid leses. Til høyre i det samme skjemaet vises situasjonen etter at lest del er behandlet. I midten vises hva som til enhver tid befinner seg på stakken.

29 Postfix --> Verdi - Figur
8 / 2 ^ * * = 7 a / b ^ c + d * e - a * c Gjenstår å lage en rutine for beregning av et aritmetisk uttrykk på postfix-form. 7 a b c ^ / d e * + a c * - Verdi Postfix 3 2 8 Det gjenstår nå å lage et delprogram for beregning av verdien av et aritmetisk uttrykk som er skrevet på postfix-form. Til dette benytter vi ytterligere en stakk, verdi-stakken. Prinsippet for et slikt program er følgende: - les fortløpende operand/operator (Sp) fra postfix-uttrykket. - Hvis operand er lest, push operandens verdi på verdi-stakken. - Hvis operator er lest, pop to verdier fra stakken, anvend operatoren på dem og push resultatet tilbake til verdi-stakken. Verdi-stakk - Les fortløpende operand/operator fra postfix-uttrykk. - Hvis operand er lest, pushes operandens verdi på verdi-stakken. - Hvis operator er lest, pop to verdier fra verdi-stakken, anvend operatoren på dem og push resultatet tilbake til verdi-stakken.

30 Postfix --> Verdi - Algoritme
postfixVerdi (postfix) /* Rutinen beregner og returnerer en verdi fra postfix */ ... Les(sp) // les WHILE sp != slutt IF sp IN operator // operator pop(verdiStakk,v2) pop(verdiStakk,v1) v := eval(v1,v2,sp) ELSE // operand v := value(sp) ENDIF push(verdiStakk,v) les(sp) ENDWHILE pop(verdiStakk,verdi) return verdi postfixVerdi beregner verdien av et aritmetisk uttrykk skrevet på postfix-form. Vi benytter en funksjon value som returnerer verdien assosiert med en operand. Tilsvarende benytter vi en funksjon eval(v1,v2,op) som utfører operasjonen v1 op v2 på de to operandene v1 og v2. Vi kan da skrive følgende algoritme:

31 Postfix --> Verdi - Eks
a b c ^ / d e * + a c * - Lest tegn Verdi Stakk Postfix abc^/de*+ac*-# a bc^/de*+ac*-# b c^/de*+ac*-# c ^/de*+ac*-# ^ /de*+ac*-# / de*+ac*-# d e*+ac*-# e *+ac*-# * ac*-# ac*-# a c*-# c *-# * # # # 7 La oss nå i detalj se hva som skjer med postfix-uttrykket abc^/de*+ac*- = 823^/56*+88*- ved bruk av den foregående algoritmen. I skjemaet vises til venstre hvilken del av post-uttrykket som til enhver tid leses. Til høyre i det samme skjemaet vises situasjonen etter at lest del er behandlet. I midten vises hva som til enhver tid befinner seg på stakken.

32 Quicksort Strategi 51 33 17 59 70 87 12 62 90 22 78 60 1. Vi plukker ut ett av tallene Ref (f. eks. det første tallet (51)). 2. Vi sørger for at dette tallet Ref kommer på korrekt plass i tabellen, dvs vi sørger for at etter at Ref er plassert, så skal alle tall til venstre i tabellen være mindre enne Ref og alle tall til høyre i tabellen skal være større enn Ref (tabell med flere tall like behandles også korrekt av denne algoritmen). 3. Vi gjentar punktene 1 og 2 på hver av de to tabell-delene skilt av Ref. Vi har tidligere i dett kompendiet (kap 3.1) sett litt på en sorterings-metode som heter boble-sortering. Det finnes andre sorterings-metoder som er raskere enn boble-sortering. QUICK-SORT er en slik metode som er meget hurtig og som benytter seg av data-strukturen stakk. La oss se litt nærmere på QUICK-SORT: Vi tenker oss at vi har følgende tall-tabell (se ovenfor) som vi skal sortere i stigende rekkefølge

33 51 33 17 59 70 87 12 62 90 22 78 60 Quicksort Eks 22 33 17 12 51 87 70 62 90 59 78 60 1. Usortert tabell. 51 33 17 59 70 87 12 62 90 22 78 60 ref 2. Plasser første tall i ref. 51 33 17 59 70 87 12 62 90 22 78 60 3. Scan fra høyre inntil første tall mindre enn ref, plasser i ledig pos. 51 33 17 59 70 87 12 62 90 22 78 60 4. Scan fra venstre inntil første tall større enn ref, plasser i ledig pos. 51 22 33 17 59 70 87 12 62 90 78 60 5. Scan fra høyre inntil første tall mindre enn ref, plasser i ledig pos. 51 22 33 17 70 87 12 62 90 59 78 60 6. Scan fra venstre inntil første tall større enn ref, plasser i ledig pos. Prinsippet med QUICK-SORT er nå følgende: a) Vi plukker ut ett av tallene Ref f.eks. det første (51). b) Vi sørger for at dette tallet Ref kommer på korrekt plass i tabellen, dvs vi sørger for at etter at Ref er plassert, så skal alle tall til venstre i tabellen være mindre enn Ref og alle tall til høyre i tabellen skal være større enn Ref (tabell med flere tall like behandles også korrekt av denne algoritmen). c) Vi gjentar a) og b) på hver av de to tabell-delene skilt av Ref. 51 22 33 17 12 70 87 62 90 59 78 60 7. Scan fra høyre inntil første tall mindre enn ref (ikke mulig). Plasser ref i ledig pos. 51 22 33 17 12 87 70 62 90 59 78 60 8. Ref er korrekt plassert. Gjenta prosessen på de to resterende halvdelene. 22 33 17 12 51 87 70 62 90 59 78 60

34 Quicksort - Algoritme (1/2)
quickSort (tab, max, stakk) /* Rutinen sorterer (i stigende rekkefølge) */ /* en tabelle tab bestående av max antall elementer */ push(stakk,0,0) l := r := max REPEAT i := l j := r ref := tab[l] WHILE i < j WHILE (ref < tab[j]) AND (i < j) // scan mot venstre j := j - 1 ENDWHILE IF j != i tab[i] := tab[j] i := i + 1 ENDIF WHILE (ref > tab[j]) AND (i < j) // scan mot høyre i := i + 1 tab[j] := tab[i] j := j - 1 tab[j] := ref // plasser ref rest(stakk,i,j,l,r) // behandle resten UNTIL (r = 0) AND (l = 0) // tom stakk Vi skulle nå kunne skrive en fullstendig algoritme for QUICK-SORT. Vi benytter oss av to prosedyrer for innsetting / fjerning av to stk elementer t1 og t2 i en stakk. Push(stakk,t1,t2) og Pop(Stakk,t1,t2)

35 Quicksort - Algoritme (2/2)
rest (stakk,i,j,l,r) /* Rutinen behandler de to resterende delene av arrayen tab */ IF j = r r :=r - 1 ELSEIF i = l l := l + 1 ELSEIF (i-l) < (r-j) // behandle det minste segmentet først k := j // for å minimalisere stakk-størrelsen push(stakk,k,r) r := i - 1 ELSE k := i - 1 push(stakk,l,k) l := j + 1 ENDIF IF r <= l pop(stakk,l,r) Del-rutinen rest som tar seg av videre behandling av de to halvdelene som ref deler tabellen i.

36 Quicksort - Algoritme - Rekursjon (1/2)
quickSort (tab, l, r) /* Rutinen sorterer (i stigende rekkefølge) */ /* en tabelle tab bestående av max antall elementer */ i := l j := r ref := tab[l] WHILE i < j WHILE (ref < tab[j]) AND (i < j) // scan mot venstre j := j - 1 ENDWHILE IF j != i tab[i] := tab[j] i := i + 1 ENDIF WHILE (ref > tab[j]) AND (i < j) // scan mot høyre i := i + 1 tab[j] := tab[i] j := j - 1 tab[j] := ref // plasser ref rest(tab,i,j,l,r) // behandle resten Samme quicksort rutine, men denne gang er rutinen rekursiv.

37 Quicksort - Algoritme - Rekursjon (2/2)
rest (tab,i,j,l,r) /* Rutinen behandler de to resterende delene av arrayen tab */ IF l < j quickSort(tab,l,j-1) ENDIF IF r > i quickSort(tab,i+1,r) Del-rutinen rest ved rekursiv quicksort rutine.

38 StackA - Simple StackAsArray StackA
stackArray maxSize = 5 5 4 3 top 2 x 1 x x Vi skal nå se på noen Java-rutiner for operasjoner på en stakk. Vi starter først med en meget enkel klasse StackA hvor vi opererer på en stakk bestående av int-elementer implementert som en array. StackA har tre attributter: maxSize Maksimal størrelse på arrayen som skal fungere som en stakk bestående av int-elementer stackArray Array som skal fungere som en stakk bestående av int-elementer top Indeks som viser hvor toppen av stakken til enhver tid er StackA har to konstruktører: StackA( ) Default konstruktør. StackA(int max) Oppretter en array med plass til max antall int-elementer. top settes til -1 for å markere toppen av stakken (foreløpig tom stakk). top kan ikke her settes til 0 da default array i Java da arrayer nummereres fra 0.

39 StackA - Simpl push / pop
5 4 3 top 2 x 1 x x push (int v) Øker top med 1 og pusher deretter v ned på stakken. pop ( ) Returnerer øverste stakk-element og minker deretter top med 1.

40 StackA - Simple isEmpty / isFull
stackArray maxSize = 5 5 4 3 top 2 x 1 x x isEmpty( ) Returnerer true hvis stakken er tom, dvs top = -1. isFull ( ) Returnerer true hvis stakken er full, dvs top = maxSize.

41 StackA - Simple Test_StackA
stackArray maxSize = 5 5 4 3 top 2 5 1 3 7 Test-program for klassen StackA. Oppretter først en stakk med plass til 10 int-elementer. Pusher deretter tallene 7, 3 og 5 (i denne rekkefølgen) ned på stakken. Gjennomløper stakken vha en while-sløyfe og skriver ut elementene, da i rekkefølge:

42 StackA - Simple StackAsList - Zt (1/2)
x next Skal nå se på hvordan vi kan implementere en simpel stakk vha en liste. Vi benytter da klassen Zt som vi har sett på tidligere. Denne klassen består av et int-attributt og en next-peker som kan peke på (referere til) en annen Zt-forekomst, dvs en enkel liste.

43 StackA - Simple StackAsList - Zt (2/2)
x next Fortsettelse av Zt-klassen.

44 StackA - Simple StackAsList StackL
head Zt Zt push x next x next pop Vi lager nå en simpel stakk-klasse StackL implementert som en liste. Klassen inneholder ett attributt list av typen SList-03 (enkel åpen forlengs liste av Zt-elementer). push (Zt v) Pusher et nytt element x til stakken ved å addere dette elementet først i listen. pop ( ) Returnerer øverste stakk-element, dvs elementet først i listen. isEmpty( ) Returnerer true hvis listen er tom. Her brukes isEmpty-funksjonen tilhørende list-klassen SLiest_03.

45 StackA - Simple StackAsList Test_StackL
elem3 elem2 elem1 5 3 7 . Test-program for klassen StackL. Oppretter først en stakk implementert som en liste. Pusher deretter tallene 7, 3 og 5 (i denne rekkefølgen) ned på stakken. Gjennomløper stakken vha en while-sløyfe og skriver ut elementene, da i rekkefølge:

46 Klassehierarki - Stack
I_Comparable A_Object I_Container A_Container StackAsArray I_Stack Test Vi skal nå implementere en stakk-klasse på en mer strukturert måte hvor vi benytter vårt klasse-hierarki fra kap 05 og hvor vi da kan la stakken inneholde forekomster av en hvilken som helst klasse. I tillegg arves med alle egenskapene fra den abstrakte klassen A_Container. Stakk implementert som en array og stakk implementert som en liste implementerer begge interfacet I_Stack beskrevet på neste side. StackAsLinkedList Test I_Visitor A_Visitor Visitor_Print

47 Interface I_Stack Interface I_Stack som gir grensesnittet for datastrukturen stakk: Interfacet I_Stack skal ivareta nødvendig funksjonalitet i en stakk. push (Object obj) Plasserer obj på stakken pop ( ) Returnerer øverste stakk-element getTop ( ) Returnerer øverste stakk-element uten å fjerne det fra stakken.

48 StackA - Adv StackAsArray
Klassen StackAsArray arver fra A_Container og implementerer I_Stack. Klassen inneholder ett attributt array som kan bestå av forekomster av en hvilken som helst klasse. StackAsArray (int size) Konstruktør som oppretter en array som kan bestå av size antall forekomster av en hvilken som helst klasse. removeAll ( ) Sletter samtlige stakk-elementer.

49 StackA - Adv StackAsArray
push (Object obj) Pusher obj ned på stakken. pop ( ) Returnerer øverste stakk-element. getTop ( ) Returnerer øverste stakk-element uten å fjerne det fra stakken.

50 StackA - Adv StackAsArray
accept (I_Visitor visitor) Tar med seg en visitor (som da utfører en eller annen operasjon, avhengig av metoden visit i visitor-forekomsten) og gjennomløper samtlige array-elementer (fra start til slutt) forutsatt at isDone-metoden ikke returnerer true (hvilket innebærer at gjennomløpingen skal avsluttes).

51 StackA - Adv StackAsArray
getEnumeration ( ) Returnerer en ny forekomst av typen I_Enumeration av en indre, anonym klasse. Denne forekomsten kan benytte metoden hasMoreElements til å teste hvorvidt der finnes flere stakk-elementer som kan gjennomløpes og metoden nextElement som returnerer neste stakk-element. Gjennomløpingen utføres fra start til slutt av arrayen.

52 StackA - Adv StackAsLinkedList
Klassen StackAsList arver fra A_Container og implementerer I_Stack. Klassen inneholder ett attributt list som kan bestå av forekomster av en hvilken som helst klasse. StackAsLinkedList ( ) Konstruktør som oppretter en liste av typen DList (dobbelt lenket liste med dummy-element). Listen kan inneholde forekomster av en hvilken som helst klasse. removeAll ( ) Sletter samtlige stakk-elementer.

53 StackA - Adv StackAsLinkedList
push (Object obj) Pusher obj ned på stakken. pop ( ) Returnerer øverste stakk-element. getTop ( ) Returnerer øverste stakk-element uten å fjerne det fra stakken.

54 StackA - Adv StackAsLinkedList
accept (I_Visitor visitor) Tar med seg en visitor (som da utfører en eller annen operasjon, avhengig av metoden visit i visitor-forekomsten) og gjennomløper samtlige liste-elementer (fra start til slutt) forutsatt at isDone-metoden ikke returnerer true (hvilket innebærer at gjennomløpingen skal avsluttes).

55 StackA - Adv StackAsLinkedList
getEnumeration ( ) Returnerer en ny forekomst av typen I_Enumeration av en indre, anonym klasse. Denne forekomsten kan benytte metoden hasMoreElements til å teste hvorvidt der finnes flere stakk-elementer som kan gjennomløpes og metoden nextElement som returnerer neste stakk-element. Gjennomløpingen utføres fra start til slutt av listen.

56 StackA - Adv Test_StackAsArray
Test-program for klassen StackAsArray. Først opprettes tre objekter objA, objB og objC av typen Za, Zb og Zb henholdsvis med innhold (5), (7,9) og (2,4). Deretter opprettes en forekomst stack av klassen StackAsArray med plass til 5 elementer. En forekomst enumStack og typen I_Enumeration og en forekomst visitorPrint av klassen Visitor_Print opprettes. De tre objektene objA, objB og objC pushes (i denne rekkefølge) til stakken. enumStack benyttest til å gjennomløpe stakken for å skrive ut hvert element. accept-metoden kalles med parameter visitorPrint for gjennomløping av stakken og utskriving av hvert element. Til slutt pop’es øverste stakk-element for utskrift.

57 StackA - Adv Test_StackAslinkedList
Test-program for klassen StackAsLinkedList. Først opprettes tre objekter objA, objB og objC av typen Za, Zb og Zb henholdsvis med innhold (5), (7,9) og (2,4). Deretter opprettes en forekomst stack av klassen StackAsLinkedList. En forekomst enumStack og typen I_Enumeration og en forekomst visitorPrint av klassen Visitor_Print opprettes. De tre objektene objA, objB og objC pushes (i denne rekkefølge) til stakken. enumStack benyttest til å gjennomløpe stakken for å skrive ut hvert element. accept-metoden kalles med parameter visitorPrint for gjennomløping av stakken og utskriving av hvert element. Til slutt pop’es øverste stakk-element for utskrift.

58 END End.


Laste ned ppt "Kap 07 Stakk I dette kapitlet skal vi se på datastrukturen stakk."

Liknende presentasjoner


Annonser fra Google