Föreläsning 13 Träd
Träd Ett träd är en datastruktur som tillåter oss att modellera sådant som vi inte kan modellera med linjära datastrukturer. Ett datavetenskapligt träd består av noder med pilar emellan. Ett datavetenskapligt träd ritas upp och ner med roten överst. Ett träd representerar både en samling noder och någon form av relation mellan dem. 2
Matematisk definition av träd Ett träd är en ändlig mängd T av en eller flera noder (förbundna med riktade kanter) sådan att: - Exakt en av noderna i T har ingen föregångare. Denna kallas rot. - Resterande noder kan delas in i disjunkta mängder T1, T2,..., Tm. - Var och en av mängderna Ti är också träd. Dessa mängder Ti kallas subträd till T. (Ett subträd är kopplat till roten via en riktad kant från roten till subträdets rot.) 3
Exempel på träd: arvshierarkier Alla Javas klasser är delar av ett enda träd med Object-klassen som rot. Klasserna i Wheels är del av Javas arvshierarki. Ju närmre roten en klass ligger desto mer generell blir den. AbstractGraphic Shape RectangularShape Line Ellipse Rectangle RoundedRectangle ConversationBubble 4
Exempel på träd: komponenthierarkier Komponenthierarkier kan också modelleras som träd. Ju närmre roten desto fler komponenter. DrawingPanel JFrame JPanel ButtonPanel JRadioButton JRadioButton JRadioButton 5
Trädterminologi Rotnod: Noden utan föregångare (A i figuren). Riktad kant (edge): Pil som förbinder en nod med en annan. Föräldra-, barnnod: En nods föregångare kallas föräldranod. En nods efterföljare kallas barnnoder. Lövnod: En nod utan barn. Noddjup: Antalet kanter man måste följa för att komma från roten till noden. B A C D E F G I H Höjden på trädet: Det största noddjupet. Nodgrad: Antalet barn en nod har. 6
Binära träd och binära sökträd Binärt träd: Träd vars noder alla har grad 0, 1 eller 2 (maximalt två barn/nod). Binärt sökträd: Binärt träd för vilket gäller för alla noder att datat i vänster subträd är mindre än datat i noden och datat i höger subträd är större än det i noden. C B G A E H D F A B C D E F G H 7
Jämförelse av datastrukturer Vilken datastruktur är bäst då vi vill lagra orden i en ordlista eller personer i en telefonkatalog? Array... + sökning - sätta in, ta bort Länkad lista Binärt sökträd... + sätta in, ta bort - sökning + sökning, sätta in, ta bort... 8
Genomgång av elementen i ett träd Hur går man igenom dataelementen i ett träd på ett systematiskt sätt? Dataelementen i en linjär struktur går man vanligtvis igenom i ordning från början till slutet. Men hur gör man i ett träd? Med ett träd finns flera möjligheter. Tre av de mest kända för binära träd bygger på att man går igenom trädet genom att följa den röda pilen i figuren. START På detta sätt kommer alla noder med i C genomgången. Noderna besöks olika B F antal gånger (1, 2 eller 3 gånger) beroende på A E H hur många barn de har. D G 9
Genomgång av elementen i ett träd När ska man titta på värdet i noden? Ska man titta på det första gången man kommer till noden och sedan titta på noderna i vänsterträdet och sedan i högerträdet? (Pre-order: CBAFEDHG) Eller ska man titta på värdet i noden efter det att man tittat på vänsterträdets noder? (In-order: ABCDEFGH) Eller, som tredje alternativ, ska man titta på värdet i noden efter det att man tittat i vänsterträdets och högerträdets noder? (Post-order: ABDEGHFC) B C START F A E H D G 10
Praktiska tillämpningar Det finns praktiska tillämpningar för de olika sätten att gå igenom noderna i ett binärt träd. Exempelvis kan vi skriva upp aritmetiska uttryck (som 2 * 3) med hjälp av binära träd, där siffrorna blir lövnoder. Om vi går igenom trädet i in-order får vi den normala infix-versionen av det aritmetiska uttrycket: 2 * 3 Om vi går igenom trädet i pre-order får vi prefixversionen av det aritmetiska uttrycket: * 2 3 Om vi går igenom trädet i postorder får vi postfix-versionen av det aritmetiska uttrycket: 2 3 * * Sammanfattningsvis: infix: 2 * 3 2 3 prefix: * 2 3 postfix: 2 3 * 11
Praktiska tillämpningar Infix-notation: Den vanligste notationen för aritmetiska uttryck och den som är mest vanlig i miniräknare. Postfix-notation: Används i vissa miniräknare. Man säger även att dessa räknare använder sig av Reverse Polish Notation (RPN) efter den polske logikern Lukasiewicz som visade fördelarna med postfix framför infix (inga parenteser behövs i de aritmetiska uttrycken). Prefix-notation: I programspråket Lisp används prefix-notaionen för aritmetiska uttryck. Fördelen med detta skrivsätt blir att de aritmetiska operatorerna, som + och *, får samma syntax som alla andra funktioner. 12
Praktiska tillämpningar Infix: (2 + 3) * (5-1) Prefix: * + 2 3-5 1 Postfix: 2 3 + 5 1 - * Kommentarer: - Med infix-notationen krävs ibland parenteser för att få rätt tolkning. - I Lisp blir prefix-uttrycket: (* (+ 2 3) (- 5 1)) START * + - 2 3 5 1 13
Praktiska tillämpningar START - + * * / 5 + 5 2 3 4 7 8 Infix: 5 * 2 + 3 / 4 5 * (7 + 8) Prefix: - + * 5 2 / 3 4 * 5 + 7 8 Postfix: 5 2 * 3 4 / + 5 7 8 + * - 14
Praktiska tillämpningar Att utvärdera ett aritmetiskt uttryck i postfix-notation kan enkelt göras med hjälp av en stack. Man går igenom uttrycket från vänster till höger en gång. När man stöter på ett tal lägger man det på stacken, när man stöter på en operator poppar man stacken två gånger, utför beräkningen med de givna talen och operatorn samt lägger resultatet på stacken. Exempel: Utvärdera uttrycket 5 2 * 3 4 / + 5 7 8 + * - 5 2 5 10 10 3 4 3 0 5 10 10 10 10 8 10 57 10 57 15 5 10 75 10-65 15
Java-kod för utvärdering av postfix-uttryck //Pre: Strängen seq är ett aritmetiskt uttryck (med heltal // och operationerna +,, * och /) i postfix. //Post: resultat = det aritmetiska uttrycket utvärderat. public int postfix(string [] seq) { Stack<Integer> stack = new Stack<Integer>(); } for (int i = 0; i < seq.length; i++) { try { int tal = Integer.parseInt(seq[i]); stack.push(new Integer(tal)); } catch (NumberFormatException e) { int tal2 = stack.pop().intvalue(); int tal1 = stack.pop().intvalue(); int res = 0; if (seq[i].equals( + )) res = tal1 + tal2; else if (seq[i].equals( )) res = tal1 tal2; else if (seg[i].equals( * )) res = tal1 * tal2; else if (seq[i].equals( / )) res = tal1 / tal2; stack.push(new Integer(res)); } } return stack.pop(); 16
Insättning i ett binärt sökträd Antag att vi vill sätta in talen 7, 10, 1, 0, -3, 45, 15, 8 i den ordning de anges i ett binärt sökträd. Hur går vi då till väga? Börja med ett tomt träd och lägg 7 i rotnoden. Tag sedan 10 och då 10 > 7 sätts 10 in som högerbarnnod till 7. Fortsätt på samma sätt med resten av talen. Vad händer om talen i talföljden byter ordning? 7 START 1 10 0 8 45-3 15 Preorder: 7, 1, 0, -3, 10, 8, 45, 15 Inorder: -3, 0, 1, 7, 8, 10, 15, 45 Postorder: -3, 0, 1, 8, 15, 45, 10, 7 17
Binärt träd Antag att vi har ett binärt träd och att det skrivs ut på följande sätt: - inorder: 2, 1, 3, 9, 7, 4, 5, 0, 8 - preorder: 7, 1, 2, 3, 9, 5, 4, 8, 0 Hur ser trädet ut? (Obs! Trädet är inget binärt sökträd.) START 2 1 3 9 7 4 5 0 8 7 1 2 3 9 5 4 8 0 7 1 5 2 3 4 8 9 0 18
ADT:n Binärt sökträd Exempel på egenskaper: - Kan innehålla ett obegränsat antal element. Exempel på operationer: Beskrivning: Skapar ett tomt träd. Pre: sant Post: ett nytt tomt träd har skapats BST() Beskrivning: Kollar om ett element finns i trädet. Pre: sant Post: resultat = sant om elementet i trädet, annars falskt search(in: element, ut: boolskt värde) Beskrivning: Lägger till ett element i trädet. Pre:!search(element) Post: element är insatt i trädet insert(in: element) Beskrivning: Tar bort ett element ur trädet. Pre: search(element) Post: element borttaget ur trädet delete(in: element) Beskrivning: Kollar om trädet är tomt. Pre: sant Post: resultat = sant om trädet är tomt, annars falskt isempty(ut: boolskt värde) Beskrivning: Skriver ut trädet i preorder Pre: sant Post: resultat = sträng med trädet i preorder preorder(ut: sträng) 19
Insättning i binärt sökträd Exempel: Sätt in 5 i följande binära sökträd: 3 Pseudokod för insättning: 1 7 2 START 5 8 insertaux(siffra, rotnod) om rotnod är tom skapa en ny nod och lägg siffra i den sätt rotnod lika med den nya noden annars om siffra < siffran i rotnod vänsterbarn till rotnod = insertaux(siffra, vänsterbarn till rotnod) annars om siffra > siffran i rotnod högerbarn till rotnod = insertaux(siffra, högerbarn till rotnod) skicka tillbaka rotnod 20
Sökning i binärt sökträd Exempel: Sök 5 i följande binära sökträd: 3 Pseudokod för sökning: 1 7 2 START 5 8 searchaux(siffra, rotnod) om rotnod är tom skicka tillbaka falskt annars om siffra < siffran i rotnod skicka tillbaka searchaux(siffra, vänsterbarn till rotnod) annars om siffra > siffran i rotnod skicka tillbaka searchaux(siffra, högerbarn till rotnod) annars skicka tillbaka sant 21
Syftet med lab 6 är att: ge övning i att skriva rekursiva metoder, ge övning i att implementera datastrukturen träd, träna filhantering och träna hantering av undantag. Laborationen går ut på att komplettera ett befintligt program med vissa metoder. 22
Del av klassdiagrammet till lab 6 I klassen BST finns en egenskap, rotnoden. Observera att metoderna i de allra flesta fall är dubblerade: en metod har ett enkelt gränssnitt och den andra är rekursiv i varje par. _element _left _right En nod har tre egenskaper och förmågorna består av en konstruktor och set- och getmetoder. 23