Trær
Et eksempel: Åtterspillet To spillere som «trekker» annenhver gang I hvert trekk velges et av tallene 1, 2, 3, men ikke tallet som motspiller valgte i forrige trekk Valgte tall summeres fortløpende Hvis en spiller får summen til å bli lik 8, vinner han/hun spillet Hvis en spiller får summen til å bli over 8, taper han/hun spillet
Åtterspillet: Eksempel F: Spiller 1 (First) S: Spiller 2 (Second) F Sum S Sum 1 1 3 3 1 4 2 6 1 7 2 9 Spiller 1 vinner
Åtterspillet kan tegnes som et tre Nodene inneholder summen så langt i spillet Forbindelser mellom nodene er mulige trekk
Et spilltre for hele åtterspillet
Trær og hierarkiske data Trestrukturer er velegnet for å representere data som er ordnet innbyrdes i et nivå- eller verdi-system Dataene i treet ligger i lag eller nivåer, som tilsvarer en hierarkisk ordning Nodene kan bare ha én direkte forgjenger på nivået over i treet, men flere direkte etterfølgere på nivået under På øverste nivå er det bare én node, roten i treet, som er den eneste som ikke har noen direkte forgjenger Maksimalt antall direkte etterfølgere som hver node kan ha, bestemmer typen av tre
Eksempel: Katalogtrær i filsystemer
Eksempel: Turneringstrær
Eksempel: Familietre
Eksempel: Foreldretre (binært) Cashmere Cat Mette Solveig Anna Peder Jan Arnt Maren Aksel Randi Margit Arnold Arne Borghild Olaf
Trær av orden n Trær klassifiseres etter hvor mange etterfølgere en node maksimalt kan ha Et tre der nodene har maksimalt n etterfølgere kalles for et tre av orden n, eller et n-ært tre Trær av orden 2, binære trær, er vanlig å bruke i algoritmer for effektiv datahåndtering Store databasesystemer bruker ofte trær av svært høy orden Trær som ikke har et begrenset antall etterfølgere til hver node kalles generelle trær
Rekursiv definisjon av tre av orden n Et tre av orden n er en datastruktur som enten er: Tom (inneholder 0 noder), eller Består av en rotnode med maksimalt n etterfølgere som alle er trær av orden n
Betegnelser på nodene i et tre Rot (root): Barn (child/children): Noder som er direkte etterfølgere til en node på nivået over, er barn av denne noden Forelder (parent): Den ene noden på øverste nivå som ikke har noen forgjenger inngangen til hele treet Noden på nivået over, som er direkte forgjenger til en node (barnet), er forelder til denne noden Søsken (siblings) Noder som har samme forelder er søsken
Betegnelser på nodene i et tre (forts.) Blad (leaf /leaves): Indre node (internal node): Node som ikke har noen barn, terminalnoder Alle noder, unntatt rotnoden, som har minst ett barn, er indre noder Kant (edge): Et nodepar som angir en forbindelse mellom to noder, fra forelder til barn
Rot: A Blader: E, I, J, K, G, H, D Indre noder: B, F, C Barn/forelder/søsken: A B, C, D B E, F F I, J, K C G, H Alle kanter i treet: (A,B), (A,C), (A,D), (B,E), (B,F), (C,G), (C,H), (F,I), (F,J), (F,K)
Vei, nivå, lengde og høyde Trær aksesseres fra roten og nedover i treet, ved hele tiden å gå fra foreldre til barn Rotnoden er på nivå 0, barna til roten på nivå 1, etc. En vei i treet er listen av noder som vi går gjennom for å komme fra en bestemt node til annen node lenger ned i treet, nodene på veien er etterfølgere Lengden av en vei er antall kanter på veien Høyden av et tre er lengden av den lengste veien som finnes i hele treet fra roten til et blad
Nivå 0 Nivå 1 Nivå 2 Nivå 3 Vei: A, D, lengde 1 Lengste vei: A, B, F, I, lengde 3 Høyde: 3
Binære trær Hver node har enten 0, 1 eller 2 barn Rekursiv definisjon: Et binært tre er enten tomt, eller: Består av en rotnode og to binære trær som kalles venstre subtre og høyre subtre til roten
Venstre-høyre ordning i binære trær
Alle binære trær med n = 1, 2 og 3 noder n=1 n=2 n=3 Oppgave: Tegn alle de 14 forskjellige binære trærne som kan lages med n = 4 noder
Fulle binære trær Et binært tre er fullt hvis det har maksimalt antall noder alle nivåene i treet er helt fylt opp med noder Fullt binært tre med høyde h = 4 :
Hvor mange noder er det i et fullt binært tre med høyde h? Nivå 0: 1 = 20 node Nivå 1: 2 = 21 noder, totalt 1 + 2 = 3 Nivå 2: 4 = 22 noder, totalt 1 + 2 + 4 = 7 Nivå 3: 8 = 23 noder, totalt 1 + 2 + 4 + 8 = 15... Nivå h: 2h noder Totalt: 1 + 2 + 4 + 8 + 16 +... + 2h = 2h+1 1 noder
Hva er høyden til et fullt binært tre, uttrykt som funksjon av antall noder? n : Antall noder i et fullt binært tre h : Høyden av treet h som funksjon av n: n = 2h+1 1 n + 1 = 2h+1 log2(n + 1) = h + 1 h = log2(n + 1) 1 = log 2 n Høyden h til et fullt binært tre med n noder er lik største heltall mindre eller lik log2 n
Komplette binære trær Et binært tre er komplett hvis: Alle nivåene, unntatt muligens det nederste, er helt fulle med noder (2k noder på hvert nivå k) På det nederste nivået skal alle nodene ligge så langt til venstre i treet som mulig Kan enkelt bevises at høyden h for et komplett binært tre med n noder også er: h = log 2 n
Et komplett binært tre
Balanserte binære trær Hvis binære trær skal være effektive, må de ikke bli skjeve og ubalanserte Definisjon av et balansert binært tre: For alle noder i treet er forskjellen i høyde på nodens venstre og høyre subtre maksimalt lik 1 Kan vises at høyden av et balansert binært tre med n noder er O(log n)
Balanserte binære trær og effektivitet Algoritmer som opererer på binære trær følger ofte en vei gjennom treet fra roten ut til et blad Effektivitet og kjøretid for algoritmene blir da proporsjonal med treets høyde h Hvis treet blir ubalansert og degenererer til «nesten lenket liste», er h = O(n) og treet blir ineffektivt Hvis vi klarer å holde det binære treet balansert er alltid h = O(log n), og algoritmene blir svært effektive Det finnes flere teknikker for holde et binært tre balansert under innsetting og fjerning av noder
Implementasjoner av binære trær Med en array: Rotnoden ligger først i arrayen, på indeks 0 Vi beregner hvor barna til en node ligger lagret i arrayen, ut i fra foreldernodens indeks Med referanser/pekere: Hele treet representeres med en peker til rotnoden Hver node inneholder en peker til venstre barn og en peker til høyre barn
Standard arrayimplementasjon Rotnoden lagres på indeks 0 Venstre og høyre barn til noden på indeks i lagres på indeksene: 2 i+1 og 2 i+2 Effektivt for (nesten) komplette binære trær Ineffektivt for ubalanserte trær som mangler mye på å være komplette medfører lange arraysegmenter som blir stående ubrukte
Arrayimplementasjon, komplett tre Treet pakkes effektivt inn i arrayen, fra venstre mot høyre og fra roten og nedover
Arrayimplementasjon, ubalansert tre Sløser mye med plass fordi nivåene nesten er tomme
Implementasjon av binære trær med pekere/referanser Tilgang til hele treet gjennom en peker til rotnoden Hver node inneholder: (Referanse til) dataene som skal lagres Peker til venstre subtre/barn Peker til høyre subtre/barn Evt. andre ting som måtte trengs (peker til foreldernode, antall noder i subtrærne, høyde, nivå...)
Binære trær med pekere/referanser: Fordeler og ulemper Fordeler: «Naturlig» løsning, enkel å forstå Egner seg godt for rekursive algoritmer Allokerer bare plass til de nodene i treet som faktisk brukes Ulemper: Ikke like raskt som array, hvis trærne er komplette Mer overhead til dynamisk memoryhåndtering
Forenklinger i forhold til læreboken Læreboken implementerer binære trær med: Generisk ADT som kan lagre 'alt' Java-iteratorer For å holde fokus på prinsippene og algoritmene, og ikke på Java, forenkler vi til: Bare implementasjon med pekere Ingen ADT, «skreddersyr» i stedet noder og algoritmer for hvert enkelt eksempel Noder som bare inneholder enkle data (heltall, tegn, strenger) i tillegg til pekere til venstre og høyre barn
Søking i et usortert binært tre Hvis treet ikke er sortert/ordnet, må vi sjekke en og en node inntil: Verdien vi leter etter er funnet, eller Vi har vært innom alle nodene i treet uten å finne søkt verdi Programmeres relativt elegant med to rekursive kall Eksempel med binært tre der dataene er enkle tegn: searchbintree.java
Søking i usortert tre: Effektivitet Søking er O(n) for et usortert tre med n noder, fordi worst-case er at alle nodene må oppsøkes Men: Hvis vi klarer å holde et binært tre sortert (søketre) og også balansert (AVL-tre), vil søking i treet bli O(log n) I tillegg blir også både innsetting og fjerning av verdier O(log n) Søketrær og AVL-trær: Kapittel 11 i læreboken
Traversering av binære trær Traversering: Oppsøk hver node i treet én (og bare en) gang, på en eller annen systematisk måte Fire ulike standard traverseringer av binære trær: Preorder Inorder Postorder Bredde-først (level order)
Preorder traversering Rekkefølge: 1. Roten 2. Venstre subtre 3. Høyre subtre ABDHIEJKCFLMGNO
Inorder traversering Rekkefølge: 1. Venstre subtre 2. Roten 3. Høyre subtre HDIBJEKALFMCNGO
Postorder traversering Rekkefølge: 1. Venstre subtre 2. Høyre subtre 3. Roten HIDJKEBLMFNOGCA
Bredde-først traversering Rekkefølge: Nivå for nivå Venstre mot høyre Ovenfra og ned ABCDEFGHIJKLMNO
Implementasjon av traverseringer Pre-, in- og postorder er alle 'dybde-først' traverseringer: Programmeres enkelt med to rekursive kall Men: Hvis det skal lages en standard Java-iterator må vi enten kopiere hele treet over i en liste (læreboka) eller simulere rekursjonen med en stack Bredde-først traversering: Kan ikke implementeres rekursivt Er mer fiklete fordi vi må lagre unna barna til en node samtidig som vi oppsøker noden Bruker en kø til å lagre barn som ikke er oppsøkt Enkel demo: treetraversals.java
En anvendelse: Uttrykkstrær Binære trær passer utmerket til lagre regneuttrykk med binære operatorer: + * / Trenger ikke paranteser eller presedensregler Uttrykkene lagres slik: Alle bladene i treet er operander (tall eller variabler) Alle indre noder er binære operatorer Venstre operand ligger i operatorens venstre subtre Høyre operand ligger i operatorens høyre subtre
Uttrykkstrær: Eksempler ab ab/c (a b) * c
Uttrykkstrær: Eksempel Læreboka implementerer en evaluator for å beregne verdien av regneuttrykk i uttrykkstrær der operandene er heltall, se avsnitt 10.5 og Java-koden
Traversering av uttrykkstrær Inorder traversering av treet skriver ut regne-uttrykkene i infix (vanlig) form: ab/c Postorder traversering skriver ut regneuttrykkene i postfix form: abc/
En klassiker: «Guess the Animal» Tidlig eksempel på maskinlæring og «kunstig intelligens», fra ca. 1960 Programmet forsøker å gjette hvilket dyr brukeren tenker på, ved å stille spørsmål som alltid har svar «ja» eller «nei» Hvis programmet gjetter på feil dyr, bes brukeren om å skrive inn: Hvilket dyr han/hun tenkte på Et ja/nei spørsmål som kan brukes til å skille de to dyrene (det vi gjettet og det riktige) fra hverandre
«Maskinlæring» i GtA Programmet starter med et lite antall dyr som det kjenner til, og noen spørsmål som kan brukes til å skille dem fra hverandre Hver gang programmet gjetter feil, legges det inn et nytt dyr i «kunnskapsbasen» til programmet Det legges også inn et spørsmål som kan brukes til å skille det nye dyret fra et annet dyr Programmet blir derfor «smartere» jo mer det brukes hvis bruker legger inn riktige data
Lagring av kunnskapen i GtA Bruker et binært tre: Alle bladnodene lagrer navnet på et dyr Alle indre noder lagrer et ja/nei spørsmål
Oppdatering av kunnskapen i GtA Når vi gjetter feil: Legger inn en ny bladnode med nytt dyr Legger inn ny indre node med nytt spørsmål
Implementasjon Spiller GtA ved å gå gjennom kunnskapstreet fra roten og langs en vei helt ut til en bladnode I hver indre node vi er innom skriver vi ut spørsmålet og leser ja/nei fra bruker Går videre til venstre (nei) eller høyre (ja) barn Når vi kommer til en bladnode, gjetter vi på dyret som er lagret i bladnoden Hvis vi gjetter feil, leser vi riktig svar og nytt spørsmål fra bruker, og legger dette inn i treet Java-kode: guesstheanimal.java