Datastrukturer och algoritmer

Relevanta dokument
Datastrukturer och algoritmer

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

Datastrukturer. föreläsning 10. Maps 1

Datastrukturer. föreläsning 10. Maps 1

Föreläsning Datastrukturer (DAT036)

Enkellänkad lista header och sentinel

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

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

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

Föreläsning 13. Träd

Träd Hierarkiska strukturer

Föreläsning 9 Datastrukturer (DAT037)

Föreläsning 9 Innehåll

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

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

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

Tentamen Datastrukturer för D2 DAT 035

Programkonstruktion och. Datastrukturer

Tentamen med lösningsförslag Datastrukturer för D2 DAT 035

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

Informationsteknologi Tom Smedsaas 19 augusti 2016

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)

Tentamen Datastrukturer D DAT 036/INN960

Föreläsning 4 Datastrukturer (DAT037)

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

Datastrukturer. föreläsning 9. Maps 1

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

DAI2 (TIDAL) + I2 (TKIEK)

Ännu mera träd: 2-3-träd, B-träd, rödsvarta träd, träd Weiss, avsnitt 4.7, 11.5, 12.2, etc.

Tentamen Datastrukturer D DAT 035/INN960

Föreläsning Datastrukturer (DAT036)

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

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

Tentamen Datastrukturer D DAT 036/INN960

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

Föreläsning 4 Datastrukturer (DAT037)

Lösningar Datastrukturer TDA

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

Föreläsning 9 Innehåll

Vad har vi pratat om i kursen?

Föreläsning 10 Datastrukturer (DAT037)

Seminarium 13 Innehåll

Föreläsning 6. Sökträd: AVL-träd, Multi-Way -sökträd, B-träd TDDC70/91: DALG. Innehåll. Innehåll. 1 AVL-träd

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

Datastrukturer och algoritmer. Föreläsning 15 Inför tentamen

Programkonstruktion och. Datastrukturer

Datastrukturer. Föreläsning 5. Maps 1

Föreläsning 13 Innehåll

Abstrakta datatyper. Primitiva vektorer. Deklarera en vektor

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 Datastrukturer (DAT037)

Tentamen kl Uppgift 4. Uppgift 5

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

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

13 Prioritetsköer, heapar

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

Prov i DAT 312: Algoritmer och datastrukturer för systemvetare

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

Innehåll. Föreläsning 12. Binärt sökträd. Binära sökträd. Flervägs sökträd. Balanserade binära sökträd. Sökträd Sökning. Sökning och Sökträd

Algoritmer och datastrukturer 2012, fo rela sning 8

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

Tentamen Datastrukturer, DAT037 (DAT036)

Tommy Färnqvist, IDA, Linköpings universitet. 1 ADT Map/Dictionary Definitioner Implementation... 2

Tentamen, Algoritmer och datastrukturer

Datastrukturer, algoritmer och programkonstruktion (DVA104, VT 2015) Föreläsning 6

Föreläsning 10 Innehåll

Föreläsning 9. Sortering

Sökning. Översikt. Binärt sökträd. Linjär sökning. Binär sökning. Sorterad array. Linjär sökning. Binär sökning Hashtabeller

Föreläsning Datastrukturer (DAT037)

Teoretisk del. Facit Tentamen TDDC (6)

TDDI01 Datastrukturer och algoritmer. Ht Laborationer

Trädstrukturer och grafer

Tentamen Datastrukturer, DAT037 (DAT036)

Tentamen Datastrukturer (DAT036/DAT037/DIT960)

Hitta k största bland n element. Föreläsning 13 Innehåll. Histogramproblemet

TENTAMEN: Algoritmer och datastrukturer. Läs detta!

Tentamen Datastrukturer D DAT 035/INN960

Lösningsförslag för tentamen i Datastrukturer (DAT037) från

Datastrukturer. föreläsning 9. Maps 1

Träd och koder. Anders Björner KTH

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

Tentamen Datastrukturer (DAT036)

Föreläsning Datastrukturer (DAT036)

Föreläsning 6 Datastrukturer (DAT037)

Ekvivalensrelationer

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

Föreläsning 13 Datastrukturer (DAT037)

TDDI16 Datastrukturer och algoritmer. Prioritetsköer, heapar, Union/Find

Tentamen i Algoritmer & Datastrukturer i Java

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

Tentamen Datastrukturer D DAT 035/INN960 (med mycket kortfattade lösningsförslag)

Tildatenta Lösningsskiss

Teoretisk del. Facit Tentamen TDDC (6)

Föreläsning 13 Datastrukturer (DAT037)

Föreläsning 5 Datastrukturer (DAT037)

Facit Tentamen TDDC kl (6)

Tentamen i Algoritmer & Datastrukturer i Java

Optimala koder. Övre gräns för optimala koder. Gränser. Övre gräns för optimala koder, forts.

Optimala koder. Det existerar förstås flera koder som har samma kodordsmedellängd. Enklaste fallet är att bara byta 0:or mot 1:or.

Transkript:

Datastrukturer och algoritmer Kompendium/komplettering Tommy Olsson, Institutionen för datavetenskap, 2007.

Innehåll Inledning... 1 1 Skiplista... 3 1.1 Skiplistans uppbyggnad... 3 1.2 Operationer på skiplistor... 4 1.2.1 Skapa skiplista... 4 1.2.2 Söka i skiplista... 4 1.2.3 Insättning i skiplista... 5 1.2.4 Borttagning ur skiplista... 5 1.3 Analys... 6 1.4 Litteratur... 6 2 Träd... 7 2.1 Terminologi... 7 2.2 Trädtraversering... 9 2.3 Uttrycksträd... 10 2.3.1 Generering av uttrycksträd... 11 2.4 Sökträd... 13 2.4.1 Binära sökträd... 14 2.4.2 Balanserade binära sökträd... 15 2.4.3 AVL-träd... 17 2.4.4 AVL-trädsrotationer... 17 2.4.5 Röd-svarta träd... 20 2.4.6 Insättning i bottom-up röd-svarta träd... 21 2.4.7 Insättning i top-down röd-svarta träd... 22 2.4.8 Splayträd... 23 2.4.9 Bottom-up-splayträd... 24 2.4.10 Top-down-splayträd... 24 2.4.11 B-träd... 27 2.4.12 Insättning i B-träd... 28 2.4.13 Borttagning ur B-träd... 30 3 Hashtabeller... 33 3.1 Hashfunktioner... 33 3.1.1 Förbearbetning av nycklar... 33 3.1.2 Val av hashfunktion... 34 3.1.3 Divisionsmetoden... 34 3.2 Kollisionshantering... 35 3.2.1 Linjär sondering... 35 3.2.2 Kvadratisk sondering... 36 3.2.3 Dubbelhashning... 36 3.2.4 Separat länkning... 37 3.3 Omhashning... 37 3.4 Linjärhashning... 37 3.5 Översikt av hashfunktioner... 39 3.5.1 Sifferurvalsmetoden, sifferanalysmetoden... 39 3.5.2 Divisionsmetoden... 40 3.5.3 Mittkvadratmetoden och andra multiplikativa metoder... 40 3.5.4 Längdberoendemetoder... 42 3.5.5 Slumpmetoden... 42 iii

3.5.6 Radixmetoden... 43 3.5.7 Algebraisk kodning... 43 3.5.8 Perfekta hashfunktioner... 43 4 Prioritetskö och heap... 45 4.1 Heap... 45 4.1.1 Binär heap... 46 4.1.2 Insättning i binär min-heap... 46 4.1.3 Borttagning ur binär min-heap... 48 5 Sortering... 51 5.1 Interna sorteringsmetoder... 52 5.1.1 Bubblesort... 52 5.1.2 Shakersort... 53 5.1.3 Selectionsort... 53 5.1.4 Insertionsort... 54 5.1.5 Shellsort... 55 5.1.6 Heapsort... 57 5.1.7 Mergesort... 58 5.1.8 Quicksort... 60 5.2 Distributiva sorteringsmetoder... 64 5.2.1 Count Sort... 64 5.2.2 Radix Sort... 65 5.3 Externa sorteringsmetoder... 66 5.3.1 Balanserad tvåvägs samsortering... 67 5.3.2 Naturlig tvåvägs samsortering... 68 5.3.3 Mångvägs samsortering... 68 5.3.4 Polyphase Merge... 68 5.3.5 Replacement Selection... 69 5.4 Indirekt sortering... 71 6 Interpolationssökning... 73 6.1 Litteratur... 74 iv

Inledning Det här kompendiet avser att ge en översikt över vanligt förekommande datastrukturer och algoritmer. Vanligt förekommande kan innebära i praktisk användning eller på grund av att det finns teoretiskt intressanta aspekter eller historiskt. Underrubriken Komplettering avspeglar att kompendiet har en del uppenbara luckor då det gäller innehåll. Till exempel behandlas inte enkla listor, stack eller kö, utan endast den mer avancerade liststrukturen skiplista tas upp. Inte heller ingår mängder, grafer och annat som skulle kunna ingå i en mer heltäckande översikt. Analys av datastrukturerna och algoritmerna görs antingen inte alls eller annars mer resonemangsmässigt än teoretiskt. 1

2

1 Skiplista Skiplistan bygger på att man, i stället för den mer strikta form av balansering som används i balanserade träd, utnyttjar en sannolikhetsbaserad balansering. Detta medför att algoritmerna för insättning och borttagning i en skiplista blir både enklare och snabbare att utföra, än motsvarande algoritmer för balanserade träd. En skiplista är i grunden en ordnad, rak länkad lista. Det som skiljer skiplistan från en enkellänkad list är att vissa listnoder innehåller flera pekare framåt i strukturen. Detta möjliggör snabbare sökning än om listorna behandlas strikt sekvensiellt. Möjligheten att hoppa förbi noder har vid sökning har givit liststrukturen dess engelska namn, skip list. Skiplistan är minneseffektiv trots de extra pekarna. Den kan konstrueras med en marginell ökning av antalet pekare per listelement, samtidigt som ingen annan hjälpinformation behöver lagras i noderna. a) 2 5 6 8 11 14 17 21 24 25 31 b) 2 5 6 8 11 14 17 21 24 25 31 NIL c) 2 5 6 8 11 14 17 21 24 25 31 NIL header d) 5 8 14 2 6 11 17 21 24 25 31 NIL 1.1 Skiplistans uppbyggnad Figur 1. Exempel på enklellänkad lista och skiplistor. I en enkkellänkad lista med n element, se figur 1 a, kan man i värsta fall behöva söka igenom hela listan för att finna ett visst värde, dvs n söksteg krävs och den förväntade medelsökvägen är n/2 söksteg. Om man i varannan nod inför ytterligare en pekare som pekar två noder framåt (figur 1 b), behöver man inte undersöka mer än n/2+1 noder i värsta fall. Om man i var fjärde nod inför ytterligare en pekare som pekar fyra steg framåt (figur 1 c), får man i sämsta fall undersöka n/4+2 noder, osv. En lista uppbyggd på detta systematiska sätt kallar man en perfekt balanserad skiplista. En nod med k stycken framåtpekare kallas en nivå-k-nod. För en perfekt balanserade skiplista gäller att 50% av noderna är nivå-1-noder, 25% är nivå-2-noder, 12.5% är nivå-3-noder, osv, ordnade på ett systematiskt sätt enligt figur 1c. Om man på detta vis, dvs i var 2 i :te nod har en pekare som pekar i noder framåt, fördubblas antalet pekare i listan men det reducerar antalet noder som kan behöva att undersökas till i värsta fall 2 logn. NIL-noden som finns sist i skiplistorna är en nod som avslutar alla nivåer i skiplistan. En perfekt balanserad skiplista är effektiv att använda vid sökning, men arbetskrävande att reorganisera i samband vid insättning och borttagning. Perfekta balans är ej praktiskt användbar och man kan nöja sig med en mindre strikt, sannolikhetsbaserad bestämning av antal framåtpekare i en nyinsatt nod. En slumpgenerator används då för att bestämma nivån för en ny nod som ska sättas in. Man kan konstruera en slumpgenerator som statistiskt bibehåller fördelningen med 50% nivå-1-noder, 25% nivå-2-noder, osv, men även andra fördelningar kan användas. 3

I en sannolikhetsbaserad skiplista kommer en nods i:te framåtpekare att ange nästa nod i listan som är av nivå i eller högre, se figur 1 d, i stället för att som i den perfekt balanserade listan, ange noden som är 2 i-1 noder framåt. Insättning och borttagning av en nod kräver endast begränsad förändring av listans struktur. Framför allt berörs ej andra noders nivå. Operationer på skiplistor inleds vanligtvis med en sökning som startar i header -noden. Normal startar en sådan sökning på den aktuella nivån för listan, dvs den i figurerna översta nivån av framåtpekare. Sökning leder framåt/nedåt i strukturen, exakt hur beror på vilket värde som söks och listans aktuella innehåll: om det sökta värdet är större än det som finns i den aktuella noden, stegar man framåt på samma nivå om det sökta värdet är mindre, stegar man nedåt i pekarfältet om det sökta värdet är lika avslutas sökningen I figur 2 visas sökvägen i det fall värdet 17 söks. Sökningen börjar i header -noden på nivå 4. Den pekaren anger noden 5 (<17) och man stegar framåt på nivå 4 till nod 5 och undersöker vad dess pekare på nivå 4 anger. Det visar sig vara NIL (innehåller ett värde > 17), så man stegar ned till nivå 3 i nod 5 och undersöker vad den pekaren anger. Det är nod 24 (>17), så man stegar ned till nivå 2, osv till dess (i detta fall) sökt värde hittas. header 5 8 14 2 6 11 17 21 24 25 31 NIL 1.2 Operationer på skiplistor Figur 2. Sökväg i en skiplista vid sökning efter värdet 17. Nya listnoder skapas dels i samband med att själva listan skapas och dels då nya noder ska sättas in i listan. Innan en ny nod skapas har den nya nodens nivå slumpats fram. De framåtpekare som ej får ange nil-noden, för att ha ett definierat värde. 1.2.1 Skapa skiplista En skiplista ska initialt bestå av ett listhuvud, en header -nod och en NIL-nod. Listans nivå sätts till 1, header -noden skapar och länkas in. Därefter skapas NIL-noden och samtliga framåtpekare i header -noden får ange NIL-noden. 1.2.2 Söka i skiplista Vid sökningar som kan innebär att listan ska ändras, vid insättning eller borttagning, sparas pekarna till sådana noder som innehåller pekare som kan komma att behöva ändras i en fältstruktur, update. För sökning som endast avser att avgöra om ett visst värde finns i listan eller ej, är sådan undanlagring onödig. Sökning med undanlagring av nodpekare fungerar enligt följande. Sökt värde placeras först i nil -noden (NIL-noden fungerar som en vaktpost, sentinel ). Därefter startar sökningen i header -noden på den för tillfället högsta nodnivån i listan. Så länge framåtpekaren på aktuell nivå anger en nod som innehåller ett värde som är mindre än sökt värde, stegar man framåt till nästa nod på den aktuella nivån. Då ett värde som är mindre än eller lika med det sökta värdet påträffas, sparas pekaren till aktuell nod undan i pekarfältet update. Sökningen fortsätter sedan på närmast 4

undre nivå. I figur 3 visas innehållet i pekarfältet uppdate efter att att sökning genomförts efter antingen 15, 16 eller 17. update 5 8 14 2 6 11 17 21 24 25 31 NIL Figur 3. Innehållet i uppdate efter sökning efter 15, 16 eller 17. Fältet update i figur 3 ger åtkomst till de noder där framåtpekare kan behöva ändras, om man vill sätta in eller ta bort det värde som söks. I sökalgoritmen ovan startar man alltid på listans aktuella nivå och arbetar framåt/nedåt i liststrukturen. Detta är standardalgoritmen. Det finns dock möjlighet att effektivisera sökningen, vilket beskrivs i [1]. 1.2.3 Insättning i skiplista Insättning utförst i två moment. Först söks listan igenom efter det värde som ska sättas in. Detta sker enligt vad som beskrivits ovan. Då sökningen är klar har man hamnat i en nod, som antingen innehåller värdet ifråga, eller framför vilken en ny nod innehållande sökvärdet ska länkas in. I fältet update finns nu pekare till sådana noder vars framåtpekare kan behöva ändras då den nya noden sätts in. Efter sökningen kontrolleras följaktligen om den sökta nyckel finns eller ej i listan. Om en ny nod ska sättas in, beräknas först den nya nodens nivå. Om nivån blir större än listans aktuella nivå, kompletteras update med de ytterligare framåtpekare som detta kräver. Dessa ska samtliga ange header -noden, som kommer att vara den direkta föregångaren till den nya noden på dessa nytillkomna nivåer. Man kan i detta sammanhang välja att öka listans nivå med högst ett i taget, för att undvika att enstaka noder erhåller en avsevärt högre nivå än övriga noder. Den nya noden skapas, initieras och länkas in i listan. Upp till och med den nivå som den nya noden erhållit, ska framåtpekarna i de noder som anges av pekarna i update införas som framåtpekare i den nya noden. Framåtpekarna i de noder som anges av pekarna i update ska ändras till att ange den nya noden. Om man vill sätta in 15, t.ex., och noden erhåller nivån 3, så ska båda framåtpekarna i nod 14 ändras till att ange denna nya nod, liksom pekaren på nivå 3 i nod 5. Pekaren på nivå 4 i nod 5 påverkas ej. 1.2.4 Borttagning ur skiplista Vid borttagning görs en sökning identisk med den som sker vid insättning. Om sökt nyckel finns ändras alla pekare som anger den nod som ska tas bort, till att ange den nod som motsvarande pekare den nod som ska tas bort anger. Därmed är den sökta noden urlänkad ur listan. Slutligen bestäms listans nya aktuella nivå, den borttagna noden kunde ju ha varit den för tillfället enda noden med den högsta nivån i listan. Borttagning av nod 17 skulle endast påverka pekaren på den lägsta nivån i nod 14. Däremot skulle borttagning av nod 24 påverka pekarna i noderna 5, 14 och 21. 5

1.3 Analys Någon analys av skiplistor görs ej här. Vi bara konstaterar att sökning normalt är en arbetsinsats av storleksordningen O(logn). Skiplistor är genom sin relativa enkelhet att implementera, intressanta att använda i stället för balanserade träd. Skiplistan kombinerar på ett bra sätt det balanserade trädets sökeffektivitet med den raka länkade listans sekvensiella egenskap. För mer detaljer se [1]. 1.4 Litteratur William Pugh, Skip List: A Probabilistic Alternative to Balanced Trees, Communications of the ACM, Juni 1990, Vol. 33, Nr 6, sid. 668-676. 6

2 Träd Träd är strukturer som förgrenar sig. Detta innebär bl.a. att information kan organiseras på ett sätt som i många fall kan ge mycket effektiva lösningar till problem och till datalagring. Man ser sällan eller aldrig träd förekomma som direkt användbara datastrukturer, som t.ex. listor, stack, kö, hashtabeller, etc. Träd används i stället som implementeringsstrukturer för vissa slags datastrukturer. Formellt sett är träd en begränsad form av riktad acyklisk graf. Riktad innebär varje båge har en riktning, som gör att bågen kan följas från den ena av de två noder den förbinder till den andra, men ej tillbaka. Acyklisk innebär att det ej finns några vägar som leder runt i grafen, så att man kan komma tillbaka till en nod man redan besökt. Den begränsning som finns för träd i jämförelse med generella riktade acykliska grafer är att varje nod bara kan ha en annan nod som länkar till noden. Grafen till vänster i figur 4 visar ett träd, medan den tillhöger ej är ett träd, bl.a. därför att det finns en cykel b-f-c och att noden h kan nås via två olika vägar. a a b c d b c d e f h i j e f h i j Figur 4. Träd är en form av riktad acyklisk graf (t.v.) 2.1 Terminologi Den första nod man kommer till i ett träd kallas trädets rot. En nods barn är de noder som direkt kan nås från noden. En nods förälder är den unika nod som är nodens direkta föregångare. Noder som har samma förälder kallas syskon. En nod utan barn kallas löv. En nod som inte är ett löv är en inre nod. rot gren a nivå 0 inre nod b c barn till a, förälder till f och g nivå 1 d e f g syskon i j k nivå 2 h nivå 3 l löv Figur 5. Terminologi. nivå 4 En nods anfäder är samtliga noder som finns på vägen från roten ner till noden. Roten är den gemensamma anfadern för alla noder i ett träd. En nods avkomma är samtliga noder som har noden som anfader. Observera att en nod är både sin egen anfader och avkomma. En nod är en äkta anfader (äkta avkomma) till en annan nod om noden ifråga är en anfader (avkomma) till den andra noden, men det omvända inte gäller. En väg är den unika följden av grenar mellan en nod och en anfader. Väglängden till en nod avser vanligtvis antalet grenar från noden till roten. Väglängden till t.ex. noden d är 2. I sökträd är medelväglängen av speciellt intresse och den beräknas som summan av alla noders väglängd dividerad med antalet noder. 7

Roten sägs befinna sig på nivå 0 i trädet, rotens barn på nivå 1, dess barnbarn på nivå 2, osv. En nods djup, eller nivå, är vägen från roten ner till noden (nod i har djupet 3). En nods höjd är längden av den längsta vägen till ett löv (nod b har höjden 3). Ett träds höjd är dess rots höjd som alltså är den längden av den längsta vägen till något löv (trädet i figur 5 har höjd 4). Löv har höjden 0 och ett tomt träd har definitionsmässigt höjden -1. En nods grad är dess antal barn (i trädet i figur 5 har nod b grad 2, nod f grad 1 och nod h grad 0). Ett träds grad är den maximala graden hos någon av dess noder (trädet i figur 2 har följaktligen grad 2, eftersom ingen nod har fler än två barn). Ett binärt träd är ett träd med grad mindre än eller lika med 2 (trädet i figur 5 är ett binärt träd). Ett träd med grad 1 kallas degenererat. Ett träds storlek är det totala antalet noder i trädet. Ett subträds storlek är antalet noder i subträdet, inklusive subträdets rot. I ett orienterat träd kallas det ena barnet vänster barn och det andra höger barn (nod d är vänster barn till nod b och nod e är höger barn till nod b). En nod i ett binärt träd är full om den har grad 2 (noderna a, b, c och d är fulla). Ett fullt binärt träd med höjd h har enbart löv på nivå h och alla dess inre noder är fulla (se figur 6 a). Lägger man till en nod i ett fullt binärt träd kommer dess höjd att öka. Ett komplett binärt träd med höjd h är ett binärt träd som är fullt ner till nivån h-1 och med löven på nivå h är tätt packade åt vänster (se figur 6 b), fullt och tätt. a) fullt binärt träd med djup 2 b) komplett binärt träd med djup 3 c) ej komplett (eller fullt) Figur 6. Fullt och komplett träd. Fulla träd är uppenbarligen mycket speciella men de är t.ex. användbara för att analysera träds egenskaper. För ett fullt träd gäller följande: Ett fullt binärt träd med höjd h 0 har 2 h+1 1 noder. Ett fullt binärt träd med höjd h har 2 h löv (om antalet noder är n, är n/2 + 1 av dessa löv, dvs finns på djupet h). Antalet fulla noder i ett binärt träd är antalet löv minus 1. Kompletta träd är också speciella men för dessa finns praktiska tillämpningar. Genom att numrera noderna nivåvis kan de direkt avbildas på ett fält, vilket t.ex. utnyttjas för att implementera binär heap. I figur 7 visas denna avbildning från ett binärt komplett träd till ett fält. 8

1 2 3 4 5 6 7 1 2 3 4 5 6 7 8 9 10 8 9 10 11 12 13 14 15 1 2 3 4 5 6 7 8 9 10 Figur 7. Kompletta träd och deras fältimplementering. I det högra exemplet i figur 7 visas två vägar i det kompletta trädet och de element i fältet som motsvarar de noder som ingår i de två vägarna. Att lagra ett träd i ett fält kallas implicit representation. Följande samband mellan trädnoderna i ett komplett binärt träd med n noder och fältindex gäller: rotnoden har index 1 och sista lövet har index n. barnen till den i:te noden har index 2i respektive 2i+1. föräldern till den i:te noden har index i/2 (heltalsdivision). sista icke-lövet har index n/2. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Det är alltså lätt att med enkel aritmetik på indexvärden förflytta sig i ett fältlagrat komplett träd, både nivåledes och i djupled uppåt och nedåt. Detta kan generaliseras till kompletta träd av godtycklig grad. Den form ett visst träd har beror av vilken typ av träd det är fråga om och ibland av hur det byggts upp. För ett uttrycksträd bestäms formen helt av det uttryck som trädet representerar. För ett sökträd påverkas formen dels av vilken typ av sökträd det är (t.ex. AVL-träd eller splayträd), dels av den ordning som elementen sätts in i trädet. I vissa sammanhang är det enbart intressant att visa ett träds övergripande struktur, inte samtliga noder i trädet. I figur 8 visas några sådana exempel på symboliserade träd. A B a) godtyckligt träd b) komplett träd c) nod med två subträd 2.2 Trädtraversering Figur 8. Symboliserade träd. Att traversera ett träd innebär att besöka alla noder i trädet på ett systematiskt sätt. Man brukar skilja mellan två grundläggande sätt att traversera ett träd, djupet först respektive bredden först. I båda fallen kan man dessutom tänka sig att traversera från vänster till höger eller från höger till vänster. 9

a) djupet först, vänster till höger b) bredden först, vänster till höger Figur 9. Trädtraversering. I fallet djupet först kommer man att besöka varje nod tre gånger (det kan anses gälla även för löv, om man ser det som om deras tomma subträd besöks). En djupet-först-traversering från vänster till höger innebär att man först kommer till noden via dess förälder. Sedan besöker man vänster subträd och återkommer till noden. Därefter besöker man höger subträd och återkommer till noden. Sedan återvänder man till föräldranoden. I detta fall kan man variera när man ska operera på noden, när den besöks fösta gången, eller efter det att vänster subträd besökts, eller efter att höger subträd besökts. Dessa tre varianter betecknas preorder, inorder- respektive postordertraversering. Inordertraversering kan med fördel implementeras rekursivt enligt följande: Om trädet inte är tomt: besök vänster subträd operera på den aktuella noden besök höger subträd Om man av någon anledning inte kan använda rekursion, kan djupet-först-traversering implementeras iterativt med hjälp av en stack. I vissa träd har man en tredje nodreferens, som refererar till föräldranoden, och i sådana träd kan traversera iterativt med hjälp av detta. Bredden-först-traversering implementeras iterativt, med hjälp av en kö enligt följande: placera pekare till rotnoden i kön så länge inte kön är tom: hämta pekaren till nästa nod från kön placera pekare till den aktuella nodens vänsterbarn i kön placera pekare till den aktuella nodens högerbarn i kön operera på den aktuella noden 2.3 Uttrycksträd I många programspråk kan man skriva aritmetiska uttryck. I program som behandlar sådana uttryck, t.ex. en kompilatorer, används uttrycksträd för att internt representera uttryck. Uttrycket x = 5 + 2 * (a - b) / 4 motsvaras av uttrycksträdet i figur 10. Ett uttryck har en hierarkiskt struktur, vilken ges av operatorernas prioritet, associativitet och parenteser. Strukturen framhävs om man parentetriserar ett uttryck fullt ut, vilket för vårt exempel innebär (x = (5 + ((2 * (a b)) / 4))). Man ser nu tydligt att deluttrycket a b ska beräknas innan dess värde multipliceras med 2, varefter det resultatet ska divideras med 4, osv. Uttrycksträds struktur avspeglar entydigt beräkningsordningen för uttrycket utan att parenteser behöver finnas i uttrycksträd. Alla operander är löv och alla operatorer är inre noder. 10

= x + Figur 10. Uttrycksträd för uttrycket x = 5 + 2 * (a - b) / 4. Om vi traverserar uttrycksträdet i figur 10 erhåller vi följande resultat. preorder: = x + 5 / * 2 - a b 4 inorder: x = 5 + 2 * a - b / 4 postorder: x 5 2 a b - * 4 / + = 5 2 Resultatet av inordertraverseringen känner vi igen som det givna infixuttrycket, förutom att parenteserna kring a-b fattas. Ett beräkningsmässigt korrekt infixuttryck går att återskapa genom införa parenteser. Preorder- och postorderresultaten kallas för polsk notation respektive omvänd polsk notation. I polsk notation kommer operatorerna före sina operander, t.ex. kommer - före a och b, * före 2 och deluttrycket a b, osv. I omvänd polsk notation är det tvärt om, a och b kommer före, 2 och a b kommer före *, osv. Dessa notationer kallas också för parentesfria notationer, eftersom inga parenteser förekommer. Beräkningsordningen är ändå är entydig. Omvänd polsk notation är mycket användbar. T.ex. kan omvänd polsk notation mycket väl ha använts som en mellanrepresentation, för att generera uttrycksträdet ur det ursprungliga infixuttrycket. Omvänd polsk notation gör det också enkelt för ett program att beräkna ett uttrycks värde. Genom att operanderna alltid kommer alltid före den motsvarande operatorn, kan man kan läsa ett uttryck från vänster till höger och successivt göra bearbetningar, utan att behöva känna till vad som kommer längre fram i uttrycket. Generella uttrycksträd är mer komplicerade än det exempel som visas här, dvs med enbart tvåställiga (binära) operatorer. Uttryck kan även innehålla enställiga (unära) operatorer, i vissa språk treställiga (villkorsoperatorn i C, C++ och Java), samt funktionsanrop. Mer information än bara själva operanderna och operatorerna lagras dessutom i noderna. 2.3.1 Generering av uttrycksträd Ett sätt att generera det uttrycksträd som motsvarar ett infixuttryck, är att först omvandla till motsvarande postfixnotation. Omvandlingen från infix- till postfixnotation kan göras med hjälp av järnvägsalgoritmen, som använder en stack för att mellanlagra operatorer. Namnet järnvägsalgoritmen kommer av man kan se förloppet som om operanderna och operatorerna i infixuttrycket, vagnarna, körs genom en enkel rangerbangård. Bangården har ett inkommande spår och ett utgående spår. Stacken fungerar som ett stickspår, på vilket operatorer körs in från det inkommande spåret för att senare köras ut på det utgående spåret. * a / 4 b 11

Operander Postfix Infix Operatorstack Figur 11. Principen för järnvägsalgoritmen. Operander körs alltid direkt till utspåret och kan inte backas tillbaka därifrån. Detta betyder bl.a. att operandernas inbördes ordning är densamma i utdata som i indata. En operator körs alltid in på stickspåret men operatorer på stickspåret som har högre prioritet ska först köras till utspåret. Om en operator på stickspåret har samma prioritet som inkommande operatorn, avgör associativiteten på den aktuella prioritetsnivån vad som ska ske. Om vänsterassociativitet gäller ska operatorn på stickspåret köras till utspåret, annars inte. Operatorer med lägre prioritet ska stå kvar på stickspåret. Infixuttrycket x = 5 + 2 * (a b) / 4 kommer att behandlas enligt figur 12. Inkommande Stack Utgående x = 5 + 2 * (a b) / 4 = 5 + 2 * (a b) / 4 x 5 + 2 * (a b) / 4 = x + 2 * (a b) / 4 = x 5 2 * (a b) / 4 = + x 5 * (a b) / 4 = + x 5 2 (a b) / 4 = + * x 5 2 a b) / 4 = + * ( x 5 2 b) / 4 = + * ( x 5 2 a b) / 4 = + * ( x 5 2 a ) / 4 = + * ( x 5 2 a b / 4 = + * x 5 2 a b 4 = + / x 5 2 a b * = + / x 5 2 a b * 4 x 5 2 a b * 4 / + = Figur 12. Järnvägsalgoritmen applicerad på infixuttrycket x = 5 + 2 * (a - b) / 4. Med hjälp av en stack kan man sedan generera uttrycksträdet ur den omvända polska notationen. Algoritmen är enkel. För en operand skapas en ny nod, vilken placeras på stacken. För en operator skapas först en ny nod. Den översta noden på stacken görs till nodens högerbarn, nästa nod från stacken görs till nodens vänsterbarnoch den nya operatornoden placeras sedan på stacken. Uttrycket x 5 2 a b 4 / + = kommer att behandlas enligt figur 13. 12

x 5 2 a b x 5 2 x 5 * x 5 * 4 a b 2 2 a b a b x 5 / x + = * 4 5 / x + 2 * 4 5 / a b 2 * 4 a b 2 a b Figur 13. Generering av uttrycksträd ur postfixuttrycket x 5 2 a b - 4 / + =. 2.4 Sökträd Genom att systematiskt ordna data i träd kan effektiva sökstrukturer erhållas. Det finns två huvudkategorier av sökträd, dels binära sökträd och dels mångförgrenade sökträd. Binära sökträd är binära träd där elementen är ordnade. De används normalt för intern datalagring, dvs lagring i primärminnet. Ett enkelt binärt sökträdet har bara ordningen som krav. Avancerade sökträd som AVL-träd och röd-svarta träd har dessutom krav på struktur och splayträden har den s.k. splayoperationen, i båda fallen för att säkerställa effektiv sökning. Mångförgrenade träd är sökträd med hög grad. Sådana används vanligtvis för extern datalagring, dvs sekundärminneslagring. B-träd är ett exempel på denna typ av sökträd och deras grad är typiskt 100-400. Högt gradtal ger litet djup, vilket i sin tur innebär att antalet läsningar/skrivningar av sekundärminne kan hållas litet. Data i ett binärt sökträd kan antingen lagras i trädnoderna eller i en annan struktur som refereras från trädnoderna. I ett B-träd skiljer sig de inre noderna helt från lövnoderna. I lövnoderna i ett B-träd lagras alla data, medan de inre noderna enbart innehåller utvalda söknycklar och referenser till subträd. Den del av ett B-träd som utgörs av de inre noderna kan ses som en indexstruktur för löven, där data lagras. k 6 2 9 < k > k 1 5 8 12 4 11 15 Figur 14. Binärt sökträd. 13

2.4.1 Binära sökträd Ett binärt sökträd är ett binärt träd där elementen är ordnade enligt följande definition. Binärt sökträd: För varje nod gäller att elementet i noden, k, är större än elementen i det vänstra subträdet och mindre än elementen i det högra subträdet. Se det schematiska trädet till vänster i figur 14. För ett enkelt binärt sökträd finns inga andra krav än definitionen ovan. Några observationer vi kan göra för ett binärt sökträd: Det minsta elementet finner vi längst ner till vänster i trädet och det hittas genom att i varje nod följa den vänstra grenen, till dess vi når en nod som saknar vänsterbarn. Det största elementet finner vi längst ner till höger i trädet och det hittas genom att i varje nod följa den högra grenen, till dess vi når en nod som saknar högerbarn. Om vi gör en inordertraversering erhåller vi elementen i stigande ordning. Det värde som finns i den nod som besöks direkt före en viss annan nod kallas inorderföregångare. T.ex. är 5 inorderföregångare till 6. Det värde som finns i den nod som besöks direkt efter en viss annan nod kallas inorderefterföljare. T.ex. är 8 inorderefterföljare till 6. Det närmast föregående elementet (inorderföregångaren) till elementet i en inre nod hittar vi som det största elementet i den aktuella nodens vänstra subträd. Det närmast efterföljande elementet (inorderefterföljare) till elementet i en inre nod hittar vi som det minsta elementet i den aktuella nodens högra subträd. Inorderföregångare och inorderefterföljare till en inre nod är noder som antingen är ett löv eller en inre nod med endast ett barn. Detta förhållande och att det är lätt att hitta dessa noder, gör det relativt enkelt att ta bort element ur ett binärt sökträd. Sökning är en operation som används i sig, för att återfinna ett visst värde, men också som ett första steg i samband med insättning och borttagning. Sökning i ett binärt sökträd kan beskrivas med följande rekursiva algoritm: om trädet är tomt sökt värde finns ej i trädet annars, om sökt värde < elementet i aktuell nod sök elementet i vänster subträd annars, om sökt värde > elementet i aktuell nod sök elementet i höger subträd annars elementet finns i den aktuella noden Sökningen i ett någorlunda symmetriskt binärt sökträd är effektiv. För varje nod vi undersöker kan vi välja bort det ena av de två subträden till noden, vilket innebär att sökmängden i varje steg ungefär halveras. Vi får genom detta en sökning som effektivitetsmässigt är jämförbar med binärsökning i ett fält. Vid insättning utförs först en trädsökning enligt ovan. Om den resulterar i att det sökta värde redan finns i trädet, får det normalt anses som ett fel. I annat fall ska sökningen avslutas i alternativet att sökt värde ej finns och en nod med det nya elementet ska sättas in där sökningen avslutades, dvs 14

som en ny lövnod. Vid insättning av elementet 7 i det vänstra trädet i figur 15 skulle sökningen leda till det tomma vänstra subträdet till nod 8, där en ny lövnod med 7 sätts in. Ett nytt värde sätts alltså alltid in som ett löv. 6 6 2 9 2 9 1 5 8 12 1 5 8 12 4 11 15 4 7 11 15 Figur 15. Insättning i binärt sökträd. Borttagning i ett binärt sökträd är mer komplicerat än insättning. Första steget är att göra en trädsökning. Om sökningen leder till ett tomt (sub)träd finns inte elementet som ska tas bort i trädet. I annat fall stannar sökningen i någon nod och man kan särskilja tre fall: 1. Elementet som ska tas bort finns i ett löv. Lövet kan tas bort utan några andra åtgärder. Borttagning av 4 i något av träden i figur 15 föranleder ingen mer åtgärd än att noden tas bort. 2. Elementet som ska tas bort finns i en inre nod med bara ett barn. Detta är också ett enkelt fall; noden som ska tas bort ersätts med det enda barnet. Borttagning av 5 i något av träden i figur 15 innebär att noden med 5 tas bort och ersätts med dess vänstra subträd, dvs noden med 4. 3. Elementet finns i en inre nod med två barn. Detta är ett mer komplicerat fall; vi inte utan vidare kan ta bort noden eftersom den har två subträd som måste tas om hand. Lösningen är att söka upp inorderföregångaren och låta den ersätta elementet som ska tas bort. Uppflyttning av inorderföregångaren bibehåller ordningen i trädet och noden i fråga är alltid ett löv eller en inre nod med endast ett barn. Alternativt kan inorderefterföljaren väljas. En fördel med denna lösning är att trädstrukturen i stort sett inte påverkas. Vi undviker både omfattande förflyttningar och radikala förändringar trädets djup. Borttagning av 6 i trädet till vänster i figur 15, innebär att noden med 5 söks upp, 5 får ersätta 6 och den nu tomma noden där 5 fanns kan enkelt tas bort. Subträdet där 5 utgjorde rot minskar sitt djup med 1. Om de element man sätter in i ett binärt sökträd är slumpmässigt ordande, kommer trädet att få en tämligen symmetrisk form och följaktligen få goda sökegenskaper. I annat fall, eller om man vill garantera bra sökegenskaper, använder man balanserade sökträd (som AVL-träd eller röd-svart träd) eller splayträd. 2.4.2 Balanserade binära sökträd För att ett binärt sökträd ska vara effektivt att söka i krävs att trädet är balanserat, dvs någorlunda symmetriskt. Optimalt är förstås om trädet har en form som är så nära ett fullt träd som möjligt, vilket innebär att trädet har ett djup som är logaritmen av antalet element. En mycket strikt form av balans för ett binärt träd är s.k. nodbalans som kan ses som någon form av perfekt balans och definieras enligt följande. Nodbalans: för varje nod ska gälla att antalet noder i dess vänstra subträd och antalet noder i dess högra subträd får skilja med högst 1. 15

I figur 16 är träden a, b och c exempel på nodbalanserade träd med djup 2. Träden d och e är ej nodbalanserade, eftersom rotnodens vänstra subträd innehåller tre noder, medan de högra endast en respektive ingen nod och skillnaden mellan antalet noder i rotnodens subträd alltså är större än 1. a) b) c) d) e) Figur 16. Olika balanserade och obalanserade träd med höjd 2. Nodbalans är ej praktiskt användbart, eftersom arbetet att hålla ett träd balanserat blir större än de vinster man kan erhålla i samband med t.ex. sökning i trädet. Ett mindre strikt balansvillkor som också är praktiskt användbart är s.k. höjdbalans, vilket definieras enligt följande. Höjdbalans: för varje nod ska gälla att höjden hos dess vänstra subträd och höjden hos dess högra subträd får skilja med högst 1. Träden a-d i figur 16 är höjdbalanserade. Träd e är ej höjdbalanserat därför att rotnodens vänstra subträd har höjd 1, det högra har höjd -1, vilket ger höjdskillnaden 2. AVL-trädet är ett höjdbalanserat binärt sökträd. Fibonacciträd kallas en form av konstruerade binära träd som representerar träd som uppfyller definitionen på höjbalans med ett minimalt antal noder för ett visst djup. I figur 17 visas de fem första Fibonacciträden. tomt träd F 0 F 1 F 2 F 3 F 4 Figur 17. Fibonacciträd. Fibonacciträden F 0 och F 1 är givna, F0 är det tomma trädet, F1 är trädet med en enda nod. Fibonacciträden definieras enlig följande: F 0 är det tomma trädet F 1 är trädet med en nod F n = F n-1 + + F n-2 (låt F n-1 vara vänster subträd och F n-2 höger subträd under en ny rotnod) Med hjälp av Fibonacciträd kan man visa att ett höjdbalanserat träd maximalt är 44% djupare än ett perfekt balanserade träd med samma antal noder och garanterat har logaritmisk tidskomplexitet i värsta fall. 16

2.4.3 AVL-träd Ett AVL-träd är ett höjdbalanserat binär sökträd. Det tillhör också en kategori av träd som kallas självbalanserande, eftersom det ingår i insättnings- och borttagningsoperationerna att eventuella obalanser som uppkommer justeras. För att göra balanskontrollen enkel lagras i varje nod dess höjd, se trädet t.v. i figur 18. 6 2 2 10 0 1 8 0 12 0 6 2 2 10 1 1 5 0 8 0 12 0 6 2 3 10 1 2 5 8 0 0 12 1 15 0 Figur 18. AVL-träd. Insättning och borttagning i ett AVL-träd sker inledningsvis på samma sätt som insättning och borttagning i det enkla binära sökträdet. Vid insättning görs först en trädsökning och sedan sätts ett nytt löv med det nya elementet in. I AVL-trädet vandrar man sedan upp till roten, baklänges utmed sökvägen, och eventuell obalans justeras. Vid borttagning söks först noden som ska tas bort upp och sedan utförs borttagningen enligt något av de tre fall som beskrivits för borttagning ur det enkla binära sökträdet. I vilket fall kommer en nod att tas bort någonstans i trädet. Från dess förälder och upp till roten kontrolleras balansen för varje nod på vägen och eventuella obalanser justeras. Balanskontrollen i AVL-trädet innebär att för varje nod göra följande: Om höjdskillnaden mellan nodens vänstra och högra subträd 1, är noden i balans. Dess höjd justeras till det maxvärdet av de två subträdens höljder + 1. Om höjdskillnaden är lika med 2 är inte noden i balans. Detta åtgärdas genom att en nodrotation utförs och höjderna i de inblandade noderna justeras. Vid insättning av ett värde i ett AVL-träd kommer högst en obalans att inträffa. Vid borttagning däremot kan flera obalanser uppstå. 2.4.3.1 AVL-trädsrotationer Det finns fyra olika AVL-trädsrotationer, enkelrotation med vänster barn (EV), enkelrotation med höger barn (EH), dubbelrotation med vänster barn (DV) och dubbelrotation med höger barn (DH). I figur 19 visas EV. k 2 k 2 k 1 k 1 C k 1 C A k 2 A B A B B C Figur 19. Enkelrotation med vänster barn (EV) i ett AVL-träd. Trädet längst till vänster i figur 19 är ett subträd i ett AVL-träd i balans. Noden med elementet k 2 är en godtycklig inre nod, noden med elementet k 1 är dess vänsterbarn. De tre symboliserade 17

subträden A, B och C är subträd med lika höjd. De är godtyckligt stora och kan även vara tomma. Elementen i träd A < k 1 < elementen i träd B < k 2 < elementen i träd C. I det mittersta trädet i figur 19 har en nod med ett värde mindre än k 1 satts in, den skuggade noden, vilket medfört att subträdet A ökat i djup med 1. Obalans uppstår då i nod k 2, eftersom dess vänstra subträd nu är två nivåer djupare är dess högra subträd C. Balansen återställs med EV: vänster barn till obalansnoden flyttas upp, obalansnoden görs till dess högerbarn, subträdet B blir vänsterbarn till den nedflyttade obalansnoden. Det högraste trädet i figur 19 visar resultatet efter rotationen. Som framgår återställs den ursprungliga höjden i trädet, vilket innebär att ingen mer obalans kan finnas högre upp i AVL-trädet. EH är en spegelbilden av EV. Efter att själva rotationen utförts, justeras höjderna i de noder som behöver justeras. Det enda av subträden A, B och C vars höjd påverkats är det subträd där den nya noden satts in och dess höjd är redan justerad då obalansen upptäcks; det var den justeringen som medförde obalansen. Återstår enbart de två noderna som roterats. Först justeras den nedroterade noden till maxvärdet av dess subträds höjder adderat med 1. Därefter justeras den upproterade noden på samma sätt. Det senare behöver egentligen inte göras, såvida inte implementeringen av enkeltrotationerna används för att implementera dubbelrotationerna. Ett exempel på enkelrotation höger visas i figur 20. 6 2 2 10 0 1 8 0 12 0 2 0 6 2 8 0 10 2 12 1 15 0 2 0 10 6 2 12 1 1 8 0 15 0 Figur 20. Insättning av 15 ger obalans i 6 som justeras med EV. I det vänstra trädet i figur 20 ska elementet 15 sättas in. En ny lövnod sätts in till höger om 12. Därefter ska höjderna i noderna på vägen upptill roten justeras och eventuell obalans justeras. Första justeringen sker i noden 12. Dess vänstra subträd har höjd -1 och dess högra subträd har höjd 0. Höjdskillnaden är 1, vilket är tillåtet, och nodens höjd justeras till max(-1, 0) + 1, dvs 1. Nästa nod som kontrolleras är 10. Dess vänstra subträd, 8, har höjd 0 och dess högra subträd har höjd 1, dvs en skillnad på 1 och justering till höjd 2. Nästa nod att kontrollera är 6. Dess vänstra subträd har höjd 0 och dess högra subträd har höjd 2, dvs en skillnad på 2. Detta innebär obalans och den första åtgärden är att fastställa vilken typ av rotation som ska utföras. Insättning har skett i höger subträd och dessutom till höger om högerbarnet, så det är EH som ska utföras på noderna 6 och 10 (jämfört med de symboliserade träden i figur 19 innebär det att nod 6 motsvarar k 1, nod 10 k 2, nod 2 motsvarar subträdet A, noden 8 subträdet B, nod 12 subträdet C och nod 15 den skuggade noden). Efter att själva rotationen utförts, justeras först höjden i nod 6 till 1 och sedan höjden i nod 10 till 2. Om det värde som sattes in i trädet i figur 19, i stället hade fått subträdet B att öka i djup med 1, ser vi att EV inte hade återställt höjden i trädet och inte heller balansen. Den skuggade noden, skulle då sitta under subträdet B, vilket efter rotationen skulle medföra en höjdskillnad på 2 mellan subträden A och B. För att hantera en obalans som uppstått i subträdet B, krävs DV. För att illustrera dubbelrotation måste rotnoden i subträdet B och dess två subträd beaktas, se figur 21. 18

k 3 k 3 k 2 k 1 k 1 k 1 k 3 k 2 D k 2 D B C A B C A B C A D Figur 21. Dubbelrotation med vänster barn (DV) i ett AVL-träd. Trädet längst till vänster i figur 21 är ett subträd i ett AVL-träd i balans. Noden k 3 är en godtycklig inre nod, noden k 1 dess vänsterbarn och noden k 2 är i sin tur högerbarn till denna. De symboliserade subträden B och C har samma höjd, subträden A och D är lika höga och en nivå djupare än B och C. Elementen i träd A < k 1 < elementen i träd B < k 2 < elementen i träd C < k 3 < elementen i träd D. Ett specialfall uppstår när A och D är tomma träd och k 2 är det nya värde som sätts in. I det mittersta trädet i figur 21 har ett värde satts in som medfört att det subträdet B ökat i djup med 1. Vi får då obalans i noden k 3, eftersom höjdskillnaden mellan subträdet med k 1 som rot och subträdet D är 2. För att åtgärda denna obalans flyttas noden k 2 upp och görs till ny rot i subträdet, noden k 1 blir dess vänsterbarn och noden k 3 dess högerbarn. Subträdet B blir högerbarn till noden k 1 och subträdet C blir vänsterbarn till noden k 3. Trädet längst till höger figur 21 visar att denna manöver återställer den ursprungliga höjden och därmed balansen i trädet som helhet. Vi kan också notera att det inte haft någon betydelse om den nyinsatta noden i stället hamnat under subträd C. Efter att själva dubbelrotationen utförts, justeras först höjderna i de nedroterade noder och sedan justeras höljden i den upproterade noden. I figur 22 visas ett exempel på insättning där en dubbelrotation utförs. 6 2 2 10 0 1 8 0 12 0 2 0 6 2 8 1 10 2 9 0 12 0 2 0 8 6 2 10 1 1 9 0 12 0 Figur 22. Insättning av 9 ger obalans i 6 som justeras med dubbelrotation med höger barn (DH). En dubbelrotation kan implementeras med två enkelrotationer. I så fall utförs först en enkelrotation med barnbarnet och barnet till obalansnoden. Därefter görs en enkelrotation med det nu upproterade barnbarnet och obalansnoden. Detta visas i figur 23 19

k 3 k 3 k 2 k 1 k 2 k 1 k 3 k 2 D k 1 C D B C A B C B A D A Figur 23. Dubbelrotation med vänster barn (DV) utförd med två enkelrotationer (EH+EV). I AVL-träd och även i andra binära träd används begreppen utsidesnod respektive insidesnod. Detta avser var en viss nod finns relativt en annan nod (anfader). I trädet i figur 24 är alla noder i subträdet U utsidesnoder relativt G, medan alla noder i subträdet I är insidesnoder relativt g. p g U I 2.4.4 Röd-svarta träd Figur 24. Insides- respektive utsidesnoder. Röd-svarta träd (red-black trees) har blivit ett populärt alternativ till AVL-träd. Liksom AVL-träd har röd-svarta träd en i värsta fall logaritmiskt tidskomplexitet. De är också ett slags självjusterande binära sökträd. En fördel med röd-svarta träd är att de vanligtvis är snabbare än AVL-träd, eftersom insättning och borttagning kan göras med ett pass uppifrån och ned. Genom att noderna även innehåller en referens till föräldern är det dessutom möjligt att använda effektiva iterativa algoritmer. Ett röd-svart träd är ett binärt sökträd med följande egenskaper: 1. Varje nod är färgad antingen röd eller svart. 2. Roten är alltid svart. 3. Om en nod är röd måste dess barn vara svarta (flera röda noder i följd kan alltså ej förekomma). 4. Varje väg från en nod till ett löv, inklusive lövet, måste innehålla lika många svarta noder. Man brukar dessutom ha konventionen att ett tomt subträd är svart. I figur 25 visas ett röd-svart träd som har tillkommit genom insättning av elementen 17, 87, 21, 72, 25, 58, 32, 45, 63, 75, 96, 38, 11 och 51. Svarta noder är skuggade. 20

32 21 72 17 25 58 87 11 45 63 75 96 38 51 Figur 25. Ett röd-svart träd. Genom att analysera röd-svarta träd kan man visa att, om varje väg från roten till ett tomt träd innehåller s svarta noder, måste det finnas minst 2 s -1 svarta noder i trädet. Vidare, eftersom roten alltid är svart och det inte kan finnas två röda noder i följd utmed en väg, är höjden i ett röd.svart träd som mest 2 log(n+1). Sökning är därmed en operation med garanterat logaritmisk tidskomplexitet. Höjden är för övrigt typiskt densamma som höjden i ett AVL-träd av motsvarande storlek. Det finns två varianter av röd-svarta träd, bottom-up och top-down. Bottom-up avser att ett pass ner i trädet, för att t.ex. söka upp den lövposition där ett nytt värde ska sättas in, först utförs och sedan ett pass upp i trädet för att kontrollera och justera eventuella obalanser som uppstått. Top-down avser att justeringen av trädet gör redan på vägen ner i trädet och att inget pass uppåt därför behöver genomföras. AA-träd är en förenklad form av röd-svart träd som är enklare att implementera och kan vara ett bra val i vissa situationer där man vill ha ett balanserat träd där borttagning ska kunna göras. 2.4.4.1 Insättning i bottom-up röd-svarta träd En ny nod som sätts in som ett löv och färgas alltid röd. Om den skulle färgas svart, skulle detta garanterat bryta mot egenskap 4. Om föräldern är svart är insättningen klar. Om föräldern däremot är röd bryter detta mot egenskap 3, och då måste noder färgas om och/eller roteras för att få bort på varandra följande röda noder. Då föräldern till en röd nod också är röd finns det flera fall att ta hänsyn till, vart och ett med ett spegelvänt symmetrisk fall. Farföräldern är i detta fall alltid svart, annars finns det redan före insättningen två röda noder i följd. Om förälderns syskon är svart och den nya noden är ett utsides barnbarn återställs egenskap 3 med en enkelrotation och lämplig omfärgning. Om den nya noden är ett insides barnbarn återställer en dubbelrotation och lämplig omfärgning egenskap 3. Dessa två fall har generaliserats i figur 26. 21

g p x p s enkelrotation x g s C D E A B C A B D E g x p x s dubbelrotation p g s A D E A B C B C D E Figur 26. Bottom-up-splayning då splaynodens förälders syskon är svart. Om förälderns syskon (s) i stället är rött fungerar varken enkel- eller dubbelrotationen ovan, eftersom båda resulterar i flera röda noder i följd. I detta fall måste både förälderns syskon och den nya rotnoden i subträdet färgas röda, vilket visas för enkelrotationsfallet i figur 27. g p x p s enkelrotation x g s C D E A B C A B D E Figur 27. Bottom-up-splayning då splaynodens förälders syskon är rött. Även i dubbelrotationsfallet kommer rotnoden i det roterade trädet att vara röd, vilket innebär problem om föräldranoden till subträdet också är röd. Man skulle kunna fortsätta att rotera skulle uppåt, till dess två röda noder i följd inte längre förekommer, eller till dess roten nås (och som då återställs till svart), men detta innebär att ett pass tillbaka upp i trädet behövs, precis som i AVL-trädet, vilket man vill undvika. 2.4.4.2 Insättning i top-down röd-svarta träd I ett top-down-splayträd utförs färgbyten och rotationer redan på vägen ner genom trädet och detta går att göra på ett sätt som garanterar att föräldranodens syskon är svart. Detta innebär att ett rött löv kan sättas in, antingen utan ytterligare åtgärd eller med endast en ytterligare enkel- eller dubbelrotation för att justera trädet. x x Figur 28. Omkastning av färger. Proceduren är i princip enkel. Om man på vägen ner ser en nod x som har två röda barn, färgas noden röd och barnen svarta, dvs färgerna kastas om (color flip), se figur 28. Det är uppenbart att 22

antalet svarta noder på vägen mellan x och löven inte förändras, men om x:s förälder är en röd nod finns nu två röda noder i följd. Nu visar det sig att x:s förälders syskon inte kan vara röd och det innebär att någon av rotationerna i figur 26 alltid kan användas för att justera trädet för att egenskap 3 återställs. Om elementet 42 ska sättas in i träder i figur 25 ovan händer följande. När sökningen når noden 45 är det en nod som har två röda barn. Omkastning av färgen medför att 45 blir röd, 38 och 51 svarta, se figur 29 a. Detta medför två röda noder i följd, 58 och 45. Notera att syskonet till 58 är svart. Noden 45 är en utsidesnod i förhållande till farföräldern 72, så en enkelrotation utförs mellan föräldern 58 och farföräldern 72, med tillhörande omfärgning av noder, se figur 29 b. En röd nod med 42 sätts sedan in och eftersom dess förälder är svart är insättningen klar. Om föräldern varit röd hade ytterligare en rotation behövt göras. 32 32 21 72 21 58 17 25 58 87 17 25 45 72 11 45 63 75 96 11 38 51 63 87 38 51 a. efter att färgen på 45, 38, 51 kastats om. 75 96 b. efter enkelrotation 72-58 och färgbyte (58 och 72). 32 21 58 17 25 45 72 11 38 51 63 87 42 75 96 c. efter insättning av 42, som en röd nod. Figur 29. Insättning av 42 i trädet i figur 25. 2.4.5 Splayträd Splayträdet är ett självjusterande binärt sökträd med amorterad logaritmisk kostnad per operation. Detta innebär att en enskild operation kan vara kostsam men att en sekvens av operationer garanteras uppföra sig som om varje enskild operation i sekvensen uppför sig logaritmisk. Idén bakom splayträd är att ofta efterfrågade element ska befinna sig nära roten, varigenom de ska har en kortare sökväg än mindre efterfrågade element. Det är inte ovanligt med söksituationer där vissa element är mer efterfrågade än andra. Man brukar tala om 90/10-principen, vilket avser att i 90 procent av alla sökningar är det endast 10 procent av elementen som efterfrågas. Sådana situationer är ideala för splayträdet. För att efterfrågade element ska befinna sig nära roten utförs s.k. splayning. Detta görs i samband med varje sökning och innebär att det sökta elementet, om det finns, flyttas det till roten. Om inte elementet finns görs ändå splayning,. Det finns flera varianter av splayträd, bl.a. bottom-up-splayträd och top-down-splayträd. I ett bottom-up-splayträd söks först det efterfrågade elementet upp med en vanlig trädsökning. Därefter flyttas elementet till roten av trädet med hjälp av nodrotationer. Detta kräver först ett pass ner genom trädet och sedan ett pass tillbaka upp i trädet. I ett top-down-splayträd utförs splayningen 23

redan på vägen ner genom trädet och när det sökta elementet hittas kan den nod det finns i direkt göras till rotnod i trädet. Endast ett pass ner genom trädet behövs i detta fall. 2.4.5.1 Bottom-up-splayträd En sökning i ett bottom-up-splayträd inleds med en vanlig trädsökning. När det sökta elementet har hittats roteras dess nod upp till roten. Detta skulle kunna göras med en följd av enkelrotationer men det ger inget bra resultat då det gäller trädets form. I stället används dubbelrotationer, utom då splaynoden hamnat direkt under rotnoden, i vilket fall endast enkelrotation kan komma ifråga. Tre slags rotationer kan förekomma i ett bottom-up-splayträd, zig, zig-zag respektive zig-zig, var och en i en symmetrisk vänster- och högervariant. zig-left och zig-right är enkelrotationer som endast utförs då splaynoden är barn till roten, zig-left om den är vänsterbarn, zig-right om den är högerbarn. Dessa motsvarar enkelrotation i AVL-trädet, se figur 19. zig-left-zag-right är en dubbelrotation som motsvarar dubbelrotation med vänster barn i ett AVL-träd och zig-right-zag-left motsvarar AVL-trädets dubbelrotation med höger barn, se figur 21. zig-zig-left är en dubbelrotation som utförs då splaynoden är vänsterbarn till sin förälder och föräldern är vänsterbarn till sin förälder, se figur 30. Zig-zig-right utförs då splaynoden är högerbarn till sin förälder och föräldern är högerbarn till sin förälder. Zig-zig-rotationerna saknar motsvarighet i AVL-trädet. k 3 k 1 k 1 k 2 C D A B k2 k 3 A B C D Figur 30. Zig-zig-left i ett bottom-up-splayträd. Insättning i ett bottom-up-splayträd görs på samma sätt som i ett enkelt binärt sökträd varefter den nya noden med det insatta elementet, en lövnod, splayas till roten. Vid borttagning söks det värde som ska tas bort upp, vilket medför att den noden splayas till roten. Sedan söks antingen inorderföregångare eller inorderefterföljare. Om man väljer det senare innebär det att söka minsta elementet i höger subträd, vilket medför att det elementet splayas till roten av höger subträd. Eftersom den noden saknar vänsterbarn kan vänster subträd placeras där och noden med inorderefterföljare görs sedan till rotnod i playträdet, varvid noden med det värde som ska tas bort är urlänkad. Om höger subträd är tomt får roten i vänster subträd bli rot i splayträdet i stället. 2.4.5.2 Top-down-splayträd Top-down-splayning utförs redan i samband med sökningen ner genom trädet. Trädet delas upp i tre delträd, ett vänsterträd, ett mitträd och ett högerträd, i princip enligt exempel 31. 24