Träd. Ett träd kan se ut på detta sätt:

Relevanta dokument
Föreläsning 7. Träd och binära sökträd

Datastrukturer, algoritmer och programkonstruktion (DVA104, HT 2014) Föreläsning 5

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 8 Erik Nilsson, Institutionen för Datavetenskap, LiU

Föreläsning Datastrukturer (DAT036)

Föreläsning 9 Innehåll

Föreläsning 5. Träd Binära träd Binärt sökträd som ADT Implementering av binärt sökträd Travestera binärt sökträd Sökning Insättning/borttagning

Föreläsning 9 Innehåll

Föreläsning 9 Datastrukturer (DAT037)

BST implementering, huvudstruktur

Linjärt minne. Sammanhängande minne är ej flexibelt. Effektivt

Datastrukturer i kursen. Föreläsning 8 Innehåll. Träd rekursiv definition. Träd

Inlämningsuppgiften. Föreläsning 9 Innehåll. Träd. Datastrukturer i kursen

Föreläsning 7. Träd och binära sökträd

Inom datalogin brukar man använda träd för att beskriva vissa typer av problem. Om man begränsar sig till träd där varje nod förgrenar sig högst två

Träd - C&P kap. 10 speciellt binära sökträd sid. 452

Binära sökträd. Seminarium 9 Binära sökträd Innehåll. Traversering av binära sökträd. Binära sökträd Definition. Exempel på vad du ska kunna

Föreläsning 13. Träd

Symboliska konstanter const

Trädstrukturer och grafer

Träd, binära träd och sökträd. Koffman & Wolfgang kapitel 6, avsnitt 1 4

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 9 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Föreläsning 10 Innehåll. Diskutera. Inordertraversering av binära sökträd. Binära sökträd Definition

Föreläsning 10 Datastrukturer (DAT037)

TDIU01 Programmering i C++

Föreläsning Datastrukturer (DAT036)

Abstrakta datatyper. Primitiva vektorer. Deklarera en vektor

BINÄRA TRÄD. (X = pekarvärdet NULL): struct int_bt_node *pivot, *ny; X X X 12 X X 12 X X -3 X X

Lösningar Datastrukturer TDA

Föreläsning 10 Innehåll

Tentamen Datastrukturer (DAT036/DAT037/DIT960)

Programmering i C++ EDAF30 Dynamiska datastrukturer. EDAF30 (Föreläsning 11) HT / 34

Algoritmer och datastrukturer 2012, föreläsning 6

Föreläsning 11 Innehåll. Diskutera. Binära sökträd Definition. Inordertraversering av binära sökträd

Självbalanserande träd AVL-träd. Koffman & Wolfgang kapitel 9, avsnitt 1 2

Föreläsning 13 och 14: Binära träd

DAI2 (TIDAL) + I2 (TKIEK)

Tentamen, Algoritmer och datastrukturer

Självbalanserande AVL-träd Weiss, avsnitt 4.4

Programmering i C++ EDA623 Dynamiska datastrukturer. EDA623 (Föreläsning 11) HT / 31

Föreläsning 4 Datastrukturer (DAT037)

Tentamen Programmeringsteknik II Inledning. Anmälningskod:

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 8 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Algoritmer och datastrukturer

13 Prioritetsköer, heapar

Tentamen Programmeringsteknik II och NV2 (alla varianter) Skriv bara på framsidan av varje papper.

Ett generellt träd är. Antingen det tomma trädet, eller en rekursiv struktur: rot /. \ /... \ t1... tn

Tentamen Datastrukturer, DAT037 (DAT036)

Föreläsning 14. Träd och filhantering

Föreläsning 10 Innehåll. Prioritetsköer och heapar. ADT Prioritetskö. Interface för Prioritetskö. Exempel på vad du ska kunna

F5: Debriefing OU2, repetition av listor, träd och hashtabeller. Carl Nettelblad

Lösningsförslag till exempeltenta 1

Träd. Rot. Förgrening. Löv

Föreläsning 4 Innehåll. Abstrakta datatypen lista. Implementering av listor. Abstrakt datatypen lista. Abstrakt datatyp

TENTAMEN: Algoritmer och datastrukturer. Läs detta! Uppgifterna är inte avsiktligt ordnade efter svårighetsgrad.

Tabeller. Programkonstruktion. Moment 8 Om abstrakta datatyper och binära sökträd. Implementering av tabellen. Operationer på tabellen

Programmeringsmetodik DV1 Programkonstruktion 1. Moment 8 Om abstrakta datatyper och binära sökträd

Det är principer och idéer som är viktiga. Skriv så att du övertygar rättaren att du har förstått dessa även om detaljer kan vara felaktiga.

Lösningsförslag till tentamen Datastrukturer, DAT037,

Datastrukturer som passar för sökning. Föreläsning 10 Innehåll. Inordertraversering av binära sökträd. Binära sökträd Definition

Lösningsförslag till tentamen Datastrukturer, DAT037,

Föreläsning 3: Abstrakta datastrukturer, kö, stack, lista

Det är principer och idéer som är viktiga. Skriv så att du övertygar rättaren om att du har förstått dessa även om detaljer kan vara felaktiga.

Upplägg. Binära träd. Träd. Binära träd. Binära träd. Antal löv på ett träd. Binära träd (9) Binära sökträd (10.1)

Tabeller. Programkonstruktion. Moment 8 Om abstrakta datatyper och binära sökträd. Specifikationer för tabellfunktionerna. Operationer på tabellen

Seminarium 13 Innehåll

Tentamen TEN1 HI

Datastrukturer som passar för sökning. Föreläsning 11 Innehåll. Binära sökträd Definition. Inordertraversering av binära sökträd

TDDC30 Programmering i Java, Datastrukturer och Algoritmer Lektion 2. Laboration 2 Datastrukturer En liten uppgift Frågor

ADT Prioritetskö. Föreläsning 12 Innehåll. Prioritetskö. Interface för Prioritetskö. Prioritetsköer och heapar

Träd Hierarkiska strukturer

Tentamen Programmeringsteknik II Skrivtid: Hjälpmedel: Java-bok (vilken som helst) Skriv läsligt! Använd inte rödpenna!

Föreläsning 4 Innehåll

Föreläsning 4 Datastrukturer (DAT037)

Föreläsning Datastrukturer (DAT037)

Tentamen'('Datastrukturer,'algoritmer'och'programkonstruktion.'

Träd, speciellt binära sökträd. Träd allmänt

Klassen BST som definierar binära sökträd med tal som nycklar och enda data. Varje nyckel är unik dvs förekommer endast en

TENTAMEN: Algoritmer och datastrukturer. Läs detta!

TDDC30 Programmering i Java, Datastrukturer och Algoritmer Lektion 2. Länkade listor Stackar Köer MyList Iteratorer Lab 2 Exceptions Paket

Tentamen Datastrukturer (DAT036)

Föreläsning 2. Länkad lista och iterator

Tentamen Datastrukturer (DAT036)

Datastrukturer. föreläsning 3. Stacks 1

Föreläsning 2. AVL-träd, Multi-Way -sökträd, B-träd TDDD71: DALG. Innehåll. Innehåll. 1 Binära sökträd

Lägg uppgifterna i ordning. Skriv uppgiftsnummer och din kod överst i högra hörnet på alla papper.

Algoritmer och datastrukturer 2012, fo rela sning 8

Föreläsning 2 Datastrukturer (DAT037)

Tentamen Datastrukturer (DAT036)

Dugga Datastrukturer (DAT036)

Föreläsning 5 TDDC91,TDDE22,725G97: DALG. Föreläsning i Datastrukturer och algoritmer 18 september 2018

if (n==null) { return null; } else { return new Node(n.data, copy(n.next));

Föreläsning 3 Datastrukturer (DAT037)

Lösningsförslag till tentamen i EDA690 Algoritmer och Datastrukturer, Helsingborg

Vad har vi pratat om i kursen?

Länkade strukturer, parametriserade typer och undantag

Försättsblad till skriftlig tentamen vid Linköpings Universitet

Teoretisk del. Facit Tentamen TDDC kl (6) 1. (6p) "Snabba frågor" Alla svar motiveras väl.

Föreläsning Datastrukturer (DAT036)

Föreläsning 3 Datastrukturer (DAT037)

ADT Prioritetskö. Föreläsning 13 Innehåll. Prioritetskö vs FIFO-kö. Prioritetskö Exempel på användning. Prioritetsköer och heapar

Transkript:

Träd En lista är en struktur som är enkel att hantera men som inte är så effektiv ur söksynpunkt. Att leta efter en viss nod i en lista med n noder kommer i genomsnitt att kräva n/2 jämförelser. Detta är inte så effektivt. En mer avancerad struktur som blir mer komplex att hantera men som blir mer effektiv är ett träd. Ett träd är en struktur som har grenar. Detta leder till att vi får fler korta sökvägar istället för en lång. Ett träd kan se ut på detta sätt: Ett träd är uppbyggt med en mängd noder sammanbundna genom pekare på ett hierarkiskt sätt. Varje nod har en ingående pekare och ett antal utgående pekare. 1 2 Ett binärt träd är ett träd där varje nod har en ingående pekare och högst två utgående pekare. Kan alltså se ut på följande sätt: rot nod Man talar också i termer av släkträd så att underliggande noder kallas för barn, i ett binärt träd talar man om vänsterbarn och högerbarn. Vi talar om förälder eller fader och förfader. Vi talar också om olika nivåer i trädet, rotnoden utgör nivå noll, sedan ökar nivån nedåt. En nod i ett binärt träd kan se ut på följande sätt: inre nod Nyckeldel Infodel Vänsterpek Högerpek löv 3 4

Ett binärt sökträd är ett binärt träd där nodernas placering baseras på deras nyckel så att en nods vänsterpekare pekar ut en nod med en lägre nyckel och en nods högerpekare pekar ut en nod med en högre nyckel. Det kan se ut på detta sätt: Vi antar här att nycklarna är unika. Om de inte är det kan man införa ett räknarfält i noderna eller låta nodernas infodel vara en länkad lista med alla dubletter. Vad ska man ha ett träd till? Det finns ju flera goda skäl, t.ex utgör de en stor bit av denna kurs. Det finns ju en del andra skäl också. 3 5 8 Ett sökträd är en effektiv struktur att söka i. För att finna talet 4 i vårt binära träd så behövs det 3 jämförelser. Vi ser att vi kan hitta vilket tal som helst i trädet med högst 4 jämförelser trots att vi har tio noder. Ett träd blir en logproportionelig struktur till skillnad mot en lista som blir linjär. 2 4 6 9 Om vi har ett träd med 8 nivåer kan vi i det ideala fallet lägga in femhundraelva noder och hitta en godtycklig av dessa noder med max 8 jämförelser. 1 7 10 5 6 Ett träd verkar vara komplicerat att underhålla och att lägga in noder i. Detta är inte fallet. I själva verket är det tämligen trivialt att lägga in noder i ett binärt sökträd. Däremot är det lite knepigare att ta bort något ur ett träd. Trädets effektitivet avgörs av dess utseende. Det ska vara balanserat för att vara effektivt. Sämsta möjliga är om vi bara har en lång pinne åt ett håll. Då har vi ju en länkad lista. Hur kan vi då implementera träd? Datastrukturen blir ju mycket lik en lista, det enda som skiljer i själva noden är att vi har två pekare. Vi kan alltså göra en post eller en klass som innehåller tillämpliga delar. Nämligen: Nyckeldel Infodel Vänsterpekare Högerpekare Det visar sig att det kan vara fördelaktigt att i varje nod ha en referens till sin förälder också. Det ingår egentligen inte i trädet, men förenklar vissa saker. Vad ska då ingå i ADT n? 7 8

Blir ungefär som i listfallet. Vi börjar med en lågnivådel, trädnoden, som mest innehåller konstruktorer Vi kan sedan utan större problem bygga upp högre nivårutiner som lite grand kan beror på vad vi avser att göra. Sådana kan vara: search insert delete print leta efter en nod i ett träd lägger in en ny nod i ett träd tar bort en ny nod ur ett träd skriver ut hela trädets infodelar Oväntat nog blir dessa rätt triviala trots att trädet verkar så komplext. Undantaget är delete.. Sökning i ett binärt sökträd. Vi antar att alla nycklar har en unik förekomst. Börja i roten. Repetera Jämför nycklarna Om lika klara Annars om söknyckeln för stor, gå ett steg till höger Annars gå ett steg till vänster tills vi hittat rätt eller tills vi inte kan gå längre Om vi hittat en nollpekare så fanns inte sökt nod. Vi kan naturligtvis formulera detta annorlunda: Om trädet är tomt så fanns nyckeln inte Annars jämför rotnoden med sökt data Om lika så är vi klara Annars finns den ev. till vänster eller till höger 9 10 En rekursiv formulering Resonemang; Har vi en tom pekare så fanns inte noden. Om vi har en eller flera noder så måste letar vi först i roten. Finns den inte där så letar vi i de båda subträden och hittar den kanske då. Om vi har ett binärt sökträd kan algoritmen förenklas en del. Vi kan ju då utesluta ett av subträden i den rekursiva sökningen. Hur lägger vi in noder i ett binärt träd då? Verkar ju svårt. Antag att vi vill lägga in talen 4 15 9 1 8 20 Det blir på detta viset: Blir då så här: Om trädet är tomt så fanns nyckeln inte Annars Jämför rotnoden med sökt data Om lika så är vi klara Annars om för liten leta till höger annars leta till vänster a) b) c) 4 4 15 11

4 15 d) 4 15 f) 9 1 9 8 4 15 e) g) 1 9 4 15 1 9 20 8 13 14 Vi kan notera en viktig sak. All inläggning av noder sker längst ned i trädet. Vi lägger aldrig in en nod mitt i trädet. Det behövs inte. Det betyder å andra sidan att trädets utseende kommer att bero på i vilken ordning talen läggs in. Om vi tar samma tal som nyss men i ordningen 1 20 4 8 9 15 får vi följande träd Man kan formulera en inläggningsalgoritm på följande sätt: Om trädet är tomt så lägg in noden direkt. Annars Repetera Jämför vår nyckel med nodens nyckel om vår nyckel större försök gå till höger annars försök gå till vänster tills du inte kan gå längre lägg in noden där 1 4 8 20 Man kan naturligtvis formulera samma sak rekursivt Trivialfallet: roten är en nollpekare, d. v. s. trädet är tomt sätt roten att peka på vår nya nod Generella fallet: Om nyckeln mindre än rotens nyckel lägg in den nya noden i vänster subträd Annars lägg in i höger subträd. Test: Eftersom subträdet till slut blir en noll-pekare så konvergerar alltid det generella fallet mot trivialfallet. 9 15 15 16

Hur kan det fungera? Vi kommer först att anropa rutinen nånting så här: root = insert(root, nynod) Sedan kommer insert att anropa sig själv ungefär så här: root.left = insert(root.left, nynod) Så småningom blir det första argumentet en nollpekare och den kommer då att ändras till att peka på min nya nod. Eftersom root.left är en pekare som ingår i trädet så kommer min nya nod att noggrannt hängas upp i julgranen på lämplig gren. Om vi går vidare med att skriva ut alla noder i ett träd så kommer vi in på något som kallas för traversering av ett träd. Att traversera ett träd innebär att man går igenom hela trädet så att man besöker alla noder en gång. Man brukar tala om tre standardmetoder att göra detta: inorder postorder preorder in betyder emellan något och betyder i detta fallet att roten är i mitten. Alltså, vänster-roten-höger. post betyder ju efter och då får vi, vänster-höger-roten eftersom pre betyder före så får vi i det sista fallet rotenvänster-höger. Detta appliceras rekursivt på trädet. 17 18 Man kan exemplifiera detta med följande träd: Man kan formulera en sekventiell inorder algoritm på följande sätt: 1 5 3 8 2 4 6 9 7 10 Skaffa en stack av noder tillräckligt stor. Sätt pek till roten. Repetera så längre inte pek är nollpekare pusha pek pek = pek.left om stacken inte är tom poppa pek gör något med utpekad nod pek = pek.höger tills pek är nollpekare och stacken är tom. Det blir ju så här ungefär: Inorder : 1 2 3 4 5 6 7 8 9 10 Preorder: 5 3 2 1 4 8 6 7 9 10 Postorder: 1 2 4 3 7 6 10 9 8 5 pusha 5 pusha 3 pusha 2 pusha 1 poppa 1 skriv ut 1 poppa 2 skriv ut 2 poppa 3 skriv ut 3 pusha 4 poppa 4 skriv ut 4 19 20

poppa 5 skriv ut 5 pusha 8 pusha 6 poppa 6 skriv ut 6 pusha 7 poppa 7 skriv ut 7 poppa 8 skriv ut 8 pusha 9 poppa 9 skriv ut 9 pusha 10 poppa 10 skriv ut 10 klart Notera att detta kan förenklas genom vår föräldrapekare! Då behövs inte stacken. Hur blir en rekursiv formulering då? Trivialfallet: Tomt träd, gör inget Generella fallet: Ta först hand om vänster subträd Skriv sedan ut roten Ta sedan hand om höger subträd Konvergens? Ja subträden blir nollpekare så småningom. blir såhär ungefär inorder_print( root : trädpekare) om rot inte nollpekare inorder_print(root.left) skriv(root.info) inorder_print(root.right) end Ingen stack ingen repetition bara några få enkla rader. Medge att det blir snyggt! 21 22 Vi ser att om vi har ett binärt sökträd så får vi stigande nyckelordning om vi gör en inorder traversering. Om vi inte bryr oss om ordningen utan bara vill besöka alla noder en gång och utföra något, spelar det då någon roll vilken traverseringsordning jag väljer. Vi kan implementera ett binärt träd på olika sätt. Som i listfallet börjar vi med att definera en nod. Den innehåller en datadel och två pekare samt ett antal enkla funktioner på dessa. Det kan bli något åt det här hållet: package trad; // En trädnodklass med heltal som datadel // använd paketåtkomst för att TreeNode och Tree ska // kunna komma åt varandra direkt, stäng ute de som är // utanför paketet class TreeNode { int val; TreeNode parent; TreeNode left; TreeNode right; // datadel // förälder // vänsterpekare // högerpekare TreeNode() { // std konstruktor val = 0; // nollställ parent = null; right = null; // left = null; // TreeNode(int val) { // konstruktor this.val = val; // sätt värde parent = null; right = null; left = null; TreeNode(int val, TreeNode parent) { this.val = val; // sätt värde this.parent = parent; 23 24

; right = null; left = null; // kopiera ett träd rekursivt public TreeNode copy() { TreeNode t = new TreeNode(); // kopia t.val = val; // kopiera datadelen // klona barnen if(left!= null) { t.left = left.copy(); // vänsterträdet t.left.parent = t; // t är förälder if(right!= null) { t.right = right.copy(); t.right.parent = t; return t; // kopian En Iterator för vårt träd kan se ut som package trad; public interface Iterator { public boolean hasnext(); public int next(); public void remove(); 25 26 Vi kan om vi är mindre renläriga direkt arbeta med TreeNode klassen och låta ett träd vara en pekare till en TreeNode. Det är dock snyggare att explicit skapa en trädtyp som vi kan deklarera och använda. Ett binärt sökträd kan bli ungefär så här package trad; // En klass för binära sökträd, trädnodklassen // finns i samma paket med paketåtkomst för // att underlätta arbetet public class Tree { ; // hämta nästa nod, flytta referenserna public int next() { // ta bort aktuell nod. Om den har två // barn så kommer den att ersättas av sin // efterföljare, därför backar vi ett // steg i det fallet. public void remove() { // Standardkonstruktor public Tree() { root = null; private TreeNode root; // trädets rot // Kopiera trädet // en iterator private class TreeIterator implements Iterator { // aktuell nod och nästa nod private TreeNode lastreturned = null,next; // konstruktor public TreeIterator() { // kolla om det finns fler noder public boolean hasnext() { public Object clone() { Tree t = new Tree(); t.root = (TreeNode) root.copy(); return t; // ny iterator public Iterator iterator() { return new TreeIterator(); // Inläggning, bara en "wrapper" public void insert(int val) { root = insert(root, val); // Rekursiv inläggning. Eftersom vi inte kan ha // referensparametrar i Java så måste vi returnera 27 28

// resultatet istället och lägga in på rätt ställe TreeNode insert(treenode root, int val) { // Kolla om tomt träd public boolean empty() { return root == null; // Töm trädet, bara en wrapper public void clear() { // Ta bort alla noder ur trädet, behövs kanske inte? private void clear(treenode root) { ; // Leta upp angiven nod, returnera en referens // till den private TreeNode nodesearch(int val) { // ta bort nod med angiven datadel public void remove(int val) { // den som gör själva jobbet private void deleteentry(treenode p) { // Leta efter angivet värde i trädet, icke rekursivt public boolean search(int val) { // Traversera trädet, bara en wrapper public void traverse() { traverse(root); // inorder traversering void traverse(treenode root) { // ta fram efterföljande nod i inorder mening. // En hjälpfunktion private TreeNode successor(treenode e) { 29 30 Innan vi implementerar detta ska vi se hur man kan ta bort saker ur ett träd. När vi ska ta bort något ur ett träd så blir det vissa svårigheter. Det inses lätt att det inte är trivialt att såga bort en bit mitt i ett träd och samtidigt behålla trädet intakt. Med lite datoriserad ympningsteknik kan man dock fixa till det också. Antag att vi har trädet på sid 19. Man inser att det är lätt att ta bort ett löv t. ex. noden med värdet 1, men också överkomligt att ta bort en nod med bara ett barn, t. ex. 2 an. I detta fall får vi limma fast 1 an direkt under 3 an. Om vi vill ta bort noden med värdet 8 blir det svårare, vi kan inte hänga upp båda barnen under roten eftersom den då får 3 barn vilket inte är tillåtet. Istället erätter vi noden med en annan nod som är enklare att ta bort, utan att rubba trädets grundstruktur. Finns det en sådan nod. Ja två stycken, de som har värden närmast aktuell nod. Av konvention brukar man ta den som är närmast större än aktuell nod, i det här fallet 9 an. Vi vet säkert att denna nod inte kan ha något vänsterbarn. 31 32

Varför? Jo om den hade det så skulle denna nod ha ett lägre värde än vår nod men samtidigt högre än 8 an. (Följer av dess läge). Men då är den ju närmare 8 an än vårt tal vilket strider mot grundantagandet. Vi ersätter alltså 8 an med 9 an och tar bort den gamla noden med talet 9. Notera att resonemanget lika gärna kan appliceras på noden närmast mindre än aktuell nod. Det är inte speciellt tidskrävande att ta bort noder ur ett träd, det är bara det att metodiken blir lite strulig. Hur hittar vi rätt nod? Ta ett steg nedåt till höger, gå sedan så långt åt vänster som det går. Då har vi hittat rätt nod. Notera att vi har en successor metod i vår klass, den är till för att hitta efterföljaren. Använs kanske också av iteratorn. Hur kan vi nu implementera detta? sökning ganska enkelt enligt tidigare beskrivning // Leta efter angivet värde i trädet, // rekursivt public boolean search(int val) { // kolla om sökt tal finns i roten if (root.val == val) return true; // Nej, leta vidare TreeNode l = root; // så länge vi kan fortsätta och // så länge vi inte hittat rätt while (l!= null && l.val!= val) { // gå till vänster eller höger if (val < l.val) l = l.left; else l = l.right; return (l!= null); // svaret 33 34 Ta bort ett träd, enkelt med hjälp av destruktorerna // Töm trädet, bara en wrapper public void clear() { empty, trivial funktion // tomt inget att göra if (root == null) return; else { clear(root); root = null; public boolean empty() { return root == null; // Ta bort alla noder ur trädet, // behövs kanske inte? private void clear(treenode root) { // ta bort subträden rekursivt if (root.left!= null) { clear(root.left); root.left.parent = null; root.left = null; if (root.right!= null) { clear(root.right); root.right.parent = null; root.right = null; 35 36

Utskrift av alla noder i inorder ordning public void traverse() { traverse(root); // inorder traversering void traverse(treenode root) { if (root!= null) { // tomt? traverse(root.left); System.out.println(root.val); traverse(root.right); nodesearch och successor används bl. a. av remove. // Leta upp angiven nod, // returnera en referens till den private TreeNode nodesearch(int val) { TreeNode t = root; while (t!= null) { if (t.val == val) return t; else if (t.val > val) t = t.left; else t = t.right; return null; // ta fram efterföljande nod i inorder // mening. En hjälpfunktion private TreeNode successor(treenode e) { // tomt träd, ingen efterföljare if (e == null) return null; // ta ett steg åt höger om det går else if (e.right!= null) { // sedan åt vänster så långt det går TreeNode p = e.right; 37 38 while (p.left!= null) p = p.left; return p; // inget högerbarn else { // saknar högerbarn // vandra uppåt så länge du // bara är högerbarn. Stanna när // du hittar ett barn som är //vänsterbarn, returnera dess // förälder. TreeNode p = e.parent; TreeNode ch = e; while (p!= null && ch == p.right) { ch = p; p = p.parent; return p; Borttagning görs på detta sätt: // ta bort nod med angiven datadel public void remove(int val) { TreeNode p = nodesearch(val); // kolla om den fannse if (p == null) throw new NoSuchElementException(); deleteentry(p); // själva arbetshästen private void deleteentry(treenode p) { // kolla om p har två barn, i så fall // byt ut innehållet och // ta bort en annan nod if (p.left!= null && p.right!= null) { TreeNode s = successor(p); p.val = s.val; // flytta s data till p p = s; // ta bort s istället // Nu vet vi att p har högst ett barn TreeNode replace; // om vi har ett vänsterbarn, spara referens // annars spara referens till högerbarn if(p.left!= null) replace = p.left; else 39 40

replace = p.right; // om p har ett barn, länka //utbytesnoden till föräldern if (replace!= null) { // finns ett barn replace.parent = p.parent; // farfar blir pappa // se till att farfar adopterar barnbarnet // när vi slår ihjäl pappa. Kolla dock att // farfar existerar först if (p.parent == null) root = replace; // ingen farfar // om p är vänsterbarn, lägg in utbytesnod // där istället Vanliga binära sökträd lider av de är så känsliga för inläggningsordningen. Det leder till att effektiviteten är svår att förutse och den kan bli allt från log(n) till n-proportionell i ett träd. För att avhjälpa detta kan vi använda oss av olika metoder att balansera trädet. Obalans uppstår vid inläggning och borttagning av noder. Vi kan modifiera dessa operationer så att balans uppnås. En metod att göra detta är s. k. AVL-träd, uppkallade efter de ryska kamraterna Adelson-Velskij och Landis. else if (p == p.parent.left) p.parent.left = replace; // annars högerbarn else p.parent.right = replace; // inga barn, kolla om jag är // den enda i hela världen else if (p.parent == null) root = null; // ja, nu utrotad! // nej finns fler än jag, kolla om jag är // vänster eller högerbarn till min förälder. // Nollställ aktuellt ställe. else { if (p == p.parent.left) p.parent.left = null; else p.parent.right = null; 41 42 Principen för ett AVL-träd är ganska enkel. Vi inför en balansräknare i varje nod som anger skillnaden i längd mellan nodens vänster och högergren. I ett balanserat träd skall denna ha något av värdena -1, 0 eller +1. I ett AVL-träd modifierar vi sedan insert och remove så att balans alltid bibehålls oavsett inläggningsordning. 43 44