Stack En enkel, lineær datastruktur
Hva er en stack? En datastruktur der vi til enhver tid kun har tilgang til elementet som ble lagt inn sist Et nytt element legges alltid på toppen av stakken Skal vi ta ut et element, tar vi alltid det øverste Sammenlikning: Stabel med tallerkener Kalles også en LIFO-kø (Last In, First Out)
Innsetting og fjerning av data: push og pop
Operasjonene på en stack push: legge til et element øverst på stacken pop: fjerne øverste element top: se på/finne øverste element (aka peek) isempty: sjekk om stacken har null elementer size: gir antall elementer som er lagret på stacken
Anvendelser av stack Snu rekkefølger "undo i tekstbehandlere (drop-out stack) Lesing og beregning av regneuttrykk I kompilatorer f.eks. syntakssjekking og parsing av koden I operativ- og runtimesystemer, for å holde rede på funksjonskall (Alan Turing, 1946) og bruken av memory Simulering av rekursjon
Grensesnitt for en stack i Java public interface Stack<E> { // Legge et nytt element på stacken public void push(e element); //Fjerne og returnere øverste element på stakken public E pop(); //Returnerer øverste element uten å fjerne det public E peek(); // eller public E top(); // Antall elementer på stacken public int size(); } // Sjekker om stacken er tom public boolean isempty();
Anvendelse: Snu rekkefølge av input 1. For hvert tegn i input: 1.1 Push tegnet på en stack 2. Inntil stacken er tom: 2.1 Pop stack og skriv ut tegnet Java-kode
Stack implementert med array Lagrer elementene på stacken i en array med objekter, som initielt er tom Holder rede på toppen av stacken med en enkel teller som er indeks til neste ledige plass i arrayen Teller oppdateres for hver push/pop Stacken er tom når teller er lik 0 Dynamisk stack, øker lengden på array når den er full av elementer Se implementasjon i læreboka
Stack implementert som lenket liste Et stack-objekt med referanse/peker til noden på toppen av stacken, og en teller med antall elementer Noder med: Referanse/peker til neste element på stacken Referanse/peker til objekt som inneholder dataene Se avsnitt 4.4 i læreboka og demo i Javakoden som følger med læreboka
Anvendelse: Parantessjekking Kompilatorer bruker stakker blant annet til å kontrollere at paranteser kommer riktig i forhold til hverandre, d.v.s. at symbolene balanserer. For hver { må det finnes en }, for hver [ må det finnes en ], og så videre. Opptelling av antall åpningsparanteser og antall sluttparanteser er utilstrekkelig, fordi parantesene også må være riktig plassert i forhold til hverandre. Eksempel: Sekvensen [()] er riktig, mens sekvensen [(]) er feil.
Algoritme for parantessjekking 1. Lag en tom stakk 2. Les ett og ett tegn inntil slutt på input 2.1 Hvis lest tegn er en startparantes, push det på stakken 2.2 Ellers, hvis lest tegn er en sluttparantes: 2.2.1 Hvis stakken er tom, rapporter feil (mangler en startparantes) 2.2.2 Ellers, pop stakken og sjekk om symbolet som poppes er korresponderende startparantes Hvis ikke, rapporter feil (feil type parantes) 3. Hvis stakken ikke er tom når alle tegn er lest, rapporter feil (mangler minst én sluttparantes) Java-kode
Anvendelse: En postfix-maskin Vi er vant til å skrive regneuttrykk med infix notasjon -- operatorer mellom operandene: a / b a + b * c - d (a + b) * ( c - d) Infix krever presedensregler og paranteser: Beregnes fra venstre mot høyre Uttrykk inne i paranteser beregnes først Multiplikasjon og divisjon utføres før addisjon og subtraksjon
Postfix notasjon I postfix (eller omvendt polsk) notasjon skrives en (binær) operator rett etter de to operandene som den skal virke på: a b / a b c * + d - a b + c d - * Trenger ingen presedensregler eller paranteser Enkelt å beregne maskinelt/mekanisk med bruk av en stack Steinalderprogrammering brukte postfix
Noen flere eksempler Infix Postfix 1 + 9 1 9 + 1 + 4 * 2 1 4 2 * + 1 + 2 * 3 + 4 * 5 1 2 3 *+ 4 5 * +
Beregning av postfix regneuttrykk 1. Lag en ny stack S som kan lagre operander 2. Lag to referanser venstre og høyre til operander 3. For alle tegn T i i uttrykket: 3.1 Hvis T er en operand: S.push(T) 3.2 Ellers, hvis T er en operator 3.2.1 Sett høyre til S.pop() 3.2.2 Sett venstre til S.pop() 3.2.3 S.push(eval(venstre, T, høyre)) 4. Returner S.pop() eval(1,+,2) er vanlig infix evaluering: 1+2=3 Se Java-implementasjonen fra læreboka
Oversettelse fra infix til postfix Viktig problem å løse: Mennesker forstår best infix, maskinell beregning lettest i postfix Oversettelse (kompilering) av infix til postfix er grunnleggende for å utvikle gode programmerings-språk FORTRAN 1 FORmula TRANslator IBM ca. 1955 Første kommersielle programmeringsspråk som tillot skriving av regneuttrykk i infix Strategi for oversettelse: Skriver operander som leses fra infix utrykk direkte ut til postfix Lagrer operatorer på stack, skriver ut en operator rett etter at begge operandene den skal virke på er skrevet ut
Oversettelse med en operatorstack Algoritme for oversettelse av infix-uttrykk til postfix, uten paranteser og kun med operatorene +-*/ : 1. Lag en ny stack S som kan lagre operatorer 2. For alle tegn T i uttrykket: 2.1. Hvis T er en operand: Skriv ut T 2.2. Ellers, hvis T er en operator: 2.2.1. Så lenge T sin rang er lavere eller lik S.top() 2.2.1.1 Skriv ut S.pop() 2.2.2. S.push(T) 3. Pop og skriv ut alle gjenværende operatorer på S
Eksempel: Finne vei gjennom en labyrint Labyrint: 2-dimensjonal tabell med 1'er (vei) og 0'ere (hindring) Starter i øvre venstre hjørne, vil til nedre høyre hjørne private int [][] grid = { {1,1,1,0,1,1,0,0,0,1,1,1,1}, {1,0,0,1,1,0,1,1,1,1,0,0,1}, {1,1,1,1,1,0,1,0,1,0,1,0,0}, {0,0,0,0,1,1,1,0,1,0,1,1,1}, {1,1,1,0,1,1,1,0,1,0,1,1,1}, {1,0,1,0,0,0,0,1,1,1,0,0,1}, {1,0,1,1,1,1,1,1,0,1,1,1,1}, {1,0,0,0,0,0,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,1,1,1,1,1,1} };
Strategi for å finne vei Prøver etter tur alle mulig veier videre fra nåværende posisjon Følger alltid en mulig vei så langt som mulig Hvis vi kommer til en blindvei, returnererer vi til foregående posisjoner i motsatt rekkefølge, og prøver en av de andre veiene videre derfra Besøkte posisjoner må merkes, slik at vi unngår å gå i ring
Implementasjon: Labyrinten Bruker en stack til i hvert steg å lagre alle lovlige steg videre fra nåværende posisjon Lovlig steg: Går ikke ut av labyrinten Går ikke til en blokkert rute Går ikke til en rute vi har oppsøkt fra før Starter med å pushe (0, 0) på stacken Går videre til øverste posisjon på stacken inntil vi er fremme i nedre høyre hjørne eller stacken tom Se Java-koden fra læreboka