Binære søketrær
Definisjon: Et sortert tre For alle nodene i et binært søketre gjelder: Alle verdiene i nodens venstre subtre er mindre enn verdien i noden Alle verdiene i nodens høyre subtre er større eller lik verdien i noden Det binære søketreet er en sortert datastruktur: Nodene oppsøkes i stigende sortert rekkefølge ved en inorder (venstre-roten-høyre) traversering av søketreet
Alfabetisk sortert søketre Inorder traversering: Abigail, Abner, Adam, Adela, Agnes, Alex, Alice, Allen, Angela, Arthur, Audrey
Forenklinger i forhold til læreboken Læreboken implementerer binære søketrær med: Generisk ADT (som kan lagre 'alt') Både array og pekere Se avsnittene 10.2 10.4 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 binært søketre Starter i roten av treet Følger en vei gjennom treet inntil: Verdien vi søker etter er funnet, eller Vi kommer til en bladnode uten å ha funnet verdien det søkes etter Maksimalt antall steg i søkingen blir omtrent lik høyden av treet (lengden av lengste vei fra roten frem til et blad)
Eksempel: Søk etter verdien x = 3
Rekursiv algoritme for søking etter en verdi x i et binært søketre Hvis roten er lik null: x finnes ikke i treet, ferdig Hvis x = verdien i roten: x er funnet, ferdig Ellers hvis x < verdien i roten: Søk etter x i rotens venstre subtre Ellers: Søk etter x i rotens høyre subtre
Søking: Implementasjon og effektivitet Implementeres effektivt med iterasjon: En while-løkke der vi i hvert steg enten går til høyre eller venstre i treet, se Java-koden Kan også kodes rekursivt: Enklere(?) kode, men langsommere enn iterasjon Effektivitet: Verste tilfelle: Verdien vi søker finnes ikke i treet Må da gå hele veien fra roten til et blad Søking i et tre med n noder er O(log n) hvis treet er noenlunde balansert Søking blir O(n) hvis treet blir en skjev «nesten-liste»
Innsetting av ny verdi i binært søketre Starter i roten av treet Følger en vei gjennom treet inntil: Vi kommer til posisjonen der verdien kunne ha ligget (som en bladnode) hvis den var i treet Setter inn den nye noden som en bladnode på denne posisjonen Maksimalt antall steg i innsettingen blir omtrent lik høyden av treet (lengden av lengste vei fra roten frem til et blad)
Rekursiv algoritme for innsetting av en verdi x i et binært søketre Hvis roten er lik null: Sett inn ny rotnode med verdien x Ellers hvis x < verdien i roten: Sett inn x i rotens venstre subtre Ellers: Sett inn x i rotens høyre subtre Se lærebokas animasjon av innsetting
Innsetting: Implementasjon og effektivitet Implementeres effektivt med iterasjon: En while-løkke der vi i hvert steg enten går til høyre eller venstre i treet Setter inn ny node som et blad, som blir venstre eller høyre barn til sist oppsøkte node i treet, se Java-koden Effektivitet: Verste tilfelle: Går den lengste veien fra roten til et blad Innsetting i et tre med n noder er O(log n) hvis treet er noenlunde balansert Innsetting blir O(n) hvis treet degenererer til en «nesten-liste»
Innsetting kan gi ubalanse Binært søketre etter innsetting av: (a) K B P D M R (b) B K D P M R (c) B D K M P R
Fjerning av en verdi fra binært søketre Starter i roten av treet Følger en vei gjennom treet inntil vi finner verdien som skal fjernes Kan ikke ha «hull» i treet Må flytte på én node slik at den «fyller hullet» etter noden som skal fjernes Maksimalt antall steg i fjerningen blir igjen omtrent lik høyden av treet (lengden av lengste vei fra roten frem til et blad)
Fjerning av node i binært søketre: Må skille på fire ulike tilfeller 1. Noden som skal fjernes er en en bladnode (har ingen barn) 2. Noden som skal fjernes mangler venstre subtre 3. Noden som skal fjernes mangler høyre subtre 4. Noden som skal fjernes har både høyre og venstre subtre
Tilfelle 1: Fjerning av bladnode Sett forgjengernoden til å peke på null Fjerning av verdien 3:
Tilfelle 2 og 3: Fjerning av node med ett tomt subtre Sett forgjengernoden til å peke på fjernet nodes etterfølger Fjerning av verdien 5:
Tilfelle 4: Fjerning av node med to subtrær Flytt minste node (lengst til venstre) i nodens høyre subtre, til posisjonen der noden som skal fjernes står Krever oppdateringer av forgjenger (hvis det ikke er roten som fjernes) og (oftest) høyre etterfølger Fjerning av verdien 2, noden med verdi 3 flyttes:
Fjerning: Implementasjon og effektivitet Implementeres med iterasjon: En while-løkke der vi i hvert steg enten går til høyre eller venstre i treet, inntil noden som skal fjernes er funnet Fjerningen medfører en del «pekerfikling», som skilles ut i en egen metode, for å håndtere alle spesialtilfellene, Se lærebokas animasjon av fjerning og Java-koden Effektivitet: Verste tilfelle: Går den lengste veien fra roten til et blad «Pekerfiklingen» er alltid O(1) Fjerning i et tre med n noder er O(log n) hvis treet er noenlunde balansert Fjerning av noder kan ødelegge balansen i treet
En anvendelse av søketrær: Svadageneratoren Leser en fil med tekst, f.eks. engelsk eller tysk Registrerer i binære trær: Alle sekvenser av 2 (evt. 3, 4 eller 5) påfølgende tegn som finnes i teksten Alle tegn som kommer rett etter en tegnsekvens og hvor ofte disse forekom Skriver ut en ny tekst som bare inneholder de leste tegnsekvensene, der frekvensen av tegnene som kommer etter hver sekvens er den samme som i innlest tekst utskriften vil da ligne på språket som er lest inn
Eksempel: «en pen gren.» Tegnsekvenser: "en", "n ", " p", "pe", "en", "n ", " g", "gr", "re", "en", "n." "en" > 2' ', 1'.' / \ / \ 1'e' < " p" "n " > 1'p', 1'g' / / \ 1'r' < " g" "gr" "pe" > 1'n' / \ 1'e' < + "n." "re" > 1'n'
Svadageneratoren: Java-kode Versjon 0.1: Innlesning av n-sekvenser Versjon 0.2: Innlesning og datastruktur Versjon 0.3: Innlesning og oppbygging av binære søketrær Versjon 1.0: Innlesning og utskrift av svada
Selv-balanserende søketrær Operasjonene på søketrær er oftest O(log n), men dette kan ikke garanteres Selvbalanserende søketrær implementerer innsetting og fjerning av verdier slik at ubalanse i treet rettes opp hvis nødvendig Er alltid O(log n) Men: Krever mer komplisert programkode med mye «pekerfikling» Varianter: AVL-tre, Red-black tree (Java Collections), Splaytree, Scapegoat tree,...
AVL-trær Hver node i søketreet lagrer en balansefaktor: Forskjellen i høyde mellom nodens høyre og venstre subtre 0: Subtrærne er like høye Negativ : Venstre høyest Positiv: Høyre høyest AVL-tre: Alle noder har balansefaktor lik -1, 0 eller +1 Retter opp ubalanse hvis balansefaktor blir -2 eller 2 ved en innsetting eller fjerning i søketreet Kan bevises at høyden av AVL-tre alltid er O(log n)
Innsetting i AVL-tre Sett inn ny node som i et vanlig søketre Justér treet etterpå slik at Balansefaktoren i alle nodene fortsatt er korrekt Noder flyttes rundt i treet hvis nødvendig, for å sikre at ingen noder kommer i ubalanse Pivotnode: Nærmeste node på veien ned til ny node som har balansefaktor som ikke er lik 0 ( + eller ) Noder flyttes i treet med rotasjoner rundt pivotnoden Flere ulike tilfeller som må spesialbehandles
Tilfelle 1: Ingen pivotnode Alle noder på søkeveien ned til den nye noden som er satt inn har balansefaktor lik 0 Innsettingen kan ikke forårsake ubalanse Ingen omstruktureringer er nødvendig Trenger bare å justere balansefaktoren for alle nodene som ligger på søkeveien
Tilfelle 1: Eksempel
Tilfelle 2: Ny node i pivotnodens korteste subtre Det finnes en pivotnode på veien ned til ny node Ny node er satt inn i pivotnodens korteste subtre Kan ikke forårsake ubalanse i treet Trenger ikke flytte noen noder Justerer balansefaktor for alle noder fra og med pivotnoden og ned til den nye noden som er satt inn
Tilfelle 2: Eksempel
Tilfelle 3: Ny node i pivotnodens høyeste subtre Det finnes en pivotnode på veien ned til ny node Ny node er satt inn i pivotnodens høyeste subtre Pivotnoden blir ubalansert Treet må omstruktureres for å opprettholde AVLegenskapene To (fire) tilfeller av omstrukturering: Enkel rotasjon (to symmetriske tilfeller) Dobbel rotasjon (to symmetriske tilfeller)
Eksempel som krever enkel rotasjon Ny node settes inn i venstre subtre til pivotnodens etterfølger på søkestien:
Enkel rotasjon (symmetrisk for noden p3 til høyre)
Enkel rotasjon: Eksempel
Eksempel som krever dobbel rotasjon Ny node settes inn i høyre subtre til pivotnodens etterfølger på søkestien:
Dobbel rotasjon (symmetrisk for noden p3 til høyre)
Dobbel rotasjon. Eksempel
AVL-trær: Implementasjon og effektivitet Innsetting i AVL-trær er relativt «fiklete» Fjerning med rebalansering er enda verre... Men: Rebalansering er i verste fall en O(log n) operasjon AVL-trær garanterer derfor alltid O(log n) effektivitet for søking, innsetting og fjerning Hvis vi vet at dataene kommer i random rekkefølge, er det tilstrekkelig å bruke vanlig binært søketre