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
Spilltre for hele åtterspillet
Trær og hierarkiske data Trestrukturer er velegnet for å representere data som er ordnet innbyrdes i et nivå/verdi-system Dataene/nodene 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, 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 Jan / \ / \ / \ / \ Solveig Arnt Randi Arnold / \ / \ / \ / \ Anna Peder Maren Aksel Margit Arne Borghild Olaf
Rekursiv definisjon av et n-ært tre Et n-ært tre er enten Tomt (inneholder 0 noder), eller Består av en rotnode med maksimalt n etterfølgere som er n-ære trær Kalles også et tre av orden n Trær som ikke har et begrenset antall etterfølgere til hver node kalles generelle trær 'Viktigste' type trestruktur: n = 2 Binære trær
Noen begreper for nodene i et tre Rot (root): Den ene noden på øverste nivå som ikke har noen forgjenger inngangen til hele treet Barn (child/children): Nodene på nivået under som er direkte etterfølgere til en node, er barn av denne noden. Barn med samme forelder er søsken (siblings). Forelder (parent): Noden på nivået over, som er direkte forgjenger til en node (barnet) Kant (edge): Et nodepar som angir en forbindelse mellom to noder, fra forelder til barn Blad (leaf /leaves): Node som ikke har noen barn Indre node (internal node): Alle noder (unntatt rotnoden) som har minst ett barn, er indre noder
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)
Veier, nivåer 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 ay rotnode og to binære trær som kalles venstre subtre og høyre subtre til roten Algoritmer som opererer på binære trær er oftest rekursive: Rekursjonen følger en vei til venstre og/eller høyre fra hver node Bunn i rekursjonen når et subtre er tomt Effektiviteten avhenger oftest av høyden av treet
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
Hvor mange noder kan vi ha i et binært tre? Nivå 0: Maks. 1 node Nivå 1: Maks. 2 = 2 1 noder, totalt 1 + 2 = 3 Nivå 2: Maks. 4 = 2 2 noder, totalt 1 + 2 + 4 = 7 Nivå 3: Maks. 8 = 2 3 noder, totalt 1 + 2 + 4 + 8 = 15... Nivå h: Maks. 2 h noder, totalt: 1 + 2 + 4 + 8 +... + 2 h = 2 h+1 1 Et binært tre med høyde h kan ha maksimalt 2 h+1 1 noder Et binært tre kalles fullt hvis det har maksimalt antall noder Merk: Et fullt binært tre med n noder har høyde lik log n
Et fullt binært tre
Komplette binære trær I et komplett binært tre er alle nivåene helt fulle med noder (2 k noder på hvert nivå k) med mulig unntak for nederste nivå På det nederste nivået skal alle nodene ligge til venstre i treet Høyden h for et komplett binært tre med n noder er h = O(log n)
Et komplett binært tre
Binære trær bør være balanserte Effektivitet/kjøretid for algoritmer som opererer på trær er proporsjonal med treets høyde Mulig definisjon på balanse: Ingen noder har subtrær med høydeforskjell > 1 Kan vises at høyden med n noder da er O(log n) Hvis treet degenererer til «nesten lenket liste» vil høyde (og effektivitet) bli O(n)
Implementasjoner av binære trær To standard måter å lagre binære trær på: 'Statisk', i en array, der rotnoden ligger først, og vi regner oss frem til barnas indeks i arrayen Dynamisk: Med en referanse/peker til rotnoden og pekere i hver node til nodens barn Alternativt Bruk array (eller en disk) og lagre indeksen (eller diskadressen) til barna sammen med hver node Kan sees på som en 'simulering' av dynamisk implementasjon av binære trær (OS-memory) Se læreboka s. 269 270
Standard arrayimplementasjon Rotnoden lagres på indeks 0 Barna til noden på indeks i lagres på indeksene 2 i + 1 og 2 i + 2 Enkelt og svært effektivt for komplette eller nesten-komplette binære trær Ubrukelig for trær som mangler mye på å være komplette: Sløsing med plass, det blir laaaange sekvenser med memory som ikke brukes Kan fint brukes til å lagre en heap, som per def. alltid er et komplett tre (kommer senere)
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 er nesten tomme
Implementasjon med pekere/referanser 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å...) Fordeler: Egner seg godt for rekursive algoritmer, «naturlig» løsning Allokerer bare plass til nodene 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') og iteratorer Både array og pekere Se avsnittene 9.4, 9.6, 9.7 og lærebokas kode For å holde fokus på prinsippene og algoritmene, og ikke på Java, forenkler vi til: Bare implementasjon med pekere Ingen ADT, skreddersyr noder og algoritmer for hvert enkelt problem Noder som bare inneholder enkle data (heltall, tegn, strenger) og referanser til venstre og høyre barn
Søking i et usortert binært tre Gjøres ved å gå innom en og en node i treet på en eller annen systematisk måte, inntil: Elementet/verdien vi leter etter er funnet, eller Vi har vært innom alle nodene i treet uten å finne søkt element Programmeres relativt elegant rekursivt, se Java-koden Er O(n) for et usortert/uordnet tre med n noder Hvis vi klarer å holde treet sortert (søketre) og også balansert (AVL-tre), kan vi effektivisere søket til å bli O(log(n)) (kommer senere i kurset)
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 traversering Inorder traversering Postorder traversering Bredde-først traversering (level order)
Preorder traversering Rekkefølge: 1. Roten 2. Venstre subtre 3. Høyre subtre A B D H I E J K C F L M G N O
Inorder traversering Rekkefølge: 1. Venstre subtre 2. Roten 3. Høyre subtre H D I B J E K A L F M C N G O
Postorder traversering Rekkefølge: 1. Venstre subtre 2. Høyre subtre 3. Roten H I D J K E B L M F N O G C A
Bredde-først traversering Rekkefølge: Nivå for nivå Venstre mot høyre Ovenfra og ned A B C D E F G H I J K L M N O
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 forvi må lagre unna barna til en node samtidig som vi oppsøker noden Bruker en kø til å lagre barn som ikke er oppsøkt Se Java-koden
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 enkle operander (tall/variable) Alle indre noder er binære operatorer som har to barn/ subtrær Venstre operand ligger i operatorens venstre subtre Høyre operand ligger i operatorens høyre subtre Kan enkelt utvides til også å håndtere uttrykk med unære operatorer, som f.eks.: 3 + (-1) * 8 / sin(2)
Uttrykkstrær: Eksempler a b a b / 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 9.5 og Java-koden
Traversering av uttrykkstrær Inorder traversering av treet skriver ut regneuttrykkene i infix (vanlig) form: a b / c Postorder traversering skriver ut regneuttrykkene i postfix form: a b c /
En klassiker: «Guess the Animal» Tidlig eksempel på maskinlæring/ «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 Se Java-koden