21.01.2015 Views

Träd Träd Träd Träd, ex på användning: släktträd

Träd Träd Träd Träd, ex på användning: släktträd

Träd Träd Träd Träd, ex på användning: släktträd

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

Träd<br />

Ett träd består av ett antal noder och bågar.<br />

Det tomma trädet har inga noder eller bågar.<br />

• Om trädet inte är tomt så finns det en speciell nod,<br />

roten.<br />

• Varje nod c i trädet, utom roten, är med en båge<br />

förbunden med en annan nod p. p är förälder till c.<br />

cärbarn till p.<br />

• Det finns en entydig väg från roten till varje nod.<br />

Ex<br />

Träd<br />

Man kan också definiera träd rekursivt:<br />

Ett träd är antingen tomt eller så består det av en speciell<br />

nod (roten) och noll eller flera (icke-tomma) underträd<br />

T 1 ,T 2 , ... T k .<br />

Roten i vart och ett av underträden är förbundna med<br />

roten i trädet.<br />

T 1<br />

T 2<br />

T 3<br />

AD: Träd 1<br />

AD: Träd 2<br />

Träd<br />

Träd, <strong>ex</strong> på användning: släktträd<br />

Noder som har samma förälder kallas ibland syskon.<br />

Noder som saknar barn kallas löv, övriga är förgreningsnoder<br />

Karl XIV Johan<br />

1763-1844<br />

Oskar I<br />

1799-1876<br />

rot<br />

löv<br />

Karl XV<br />

1826-1872<br />

Gustaf<br />

1827-1852<br />

Oskar II<br />

1829-1907<br />

Eugenie<br />

1830-1889<br />

August<br />

1831-1873<br />

löv<br />

löv<br />

AD: Träd 3<br />

AD: Träd 4


Träd, <strong>ex</strong> på användning: spelträd<br />

Beskriver vilka<br />

spelställningar<br />

som kan uppstå till<br />

följdavdrag,<br />

utgående från en viss<br />

startställning.<br />

x o<br />

o x<br />

x o<br />

x o x o<br />

o x<br />

o x<br />

x o<br />

x o<br />

Kan användas för<br />

att hitta ”vinnande<br />

strategi” i spelet.<br />

Träd, <strong>ex</strong> på användning: representation<br />

av aritmetiska uttryck<br />

+<br />

1 *<br />

2 3<br />

1<br />

+<br />

2<br />

*<br />

3<br />

o<br />

x<br />

x<br />

o<br />

x<br />

o<br />

x<br />

o<br />

x<br />

o<br />

o<br />

x<br />

x<br />

o<br />

o<br />

x<br />

x<br />

o<br />

o<br />

x<br />

1+2*3<br />

(1 + 2) * 3<br />

x<br />

o<br />

x<br />

o<br />

x<br />

o<br />

x<br />

o<br />

x<br />

o<br />

AD: Träd 5<br />

AD: Träd 6<br />

Träd, <strong>ex</strong> på användning: binära sökträd<br />

Träd: <strong>ex</strong> på representation<br />

Hans<br />

Mona<br />

Svea<br />

class Node {<br />

Node firstChild;<br />

Node n<strong>ex</strong>tSibling;<br />

object element; //nodens<br />

//innehåll<br />

}<br />

Anna<br />

Karl<br />

Nora<br />

Tora<br />

Används för att lagra element som innehåller ”söknyckel” för vilken<br />

jämförelseoperationer är definierade. Här: sträng.<br />

Nycklarna i vänster underträd är mindre än rotens nyckel som<br />

i sin tur är mindre än nycklarna i höger underträd.<br />

Kommer vi att behandla utförligt senare.<br />

Fördel: Enkelt och snabbt att leta upp, sätta in och ta bort element.<br />

AD: Träd 7<br />

class Tree {<br />

private Node root;<br />

...<br />

}<br />

Varje nod innehåller två attribut, firstChild som refererar till det<br />

”äldsta” barnet och n<strong>ex</strong>tSibling som refererar till nästa syskon i<br />

syskonskaran. I roten är alltid n<strong>ex</strong>tSibling null. I alla löv är<br />

firstChild null. Träd-klassen innehåller ett attribut som refererar till<br />

rot-noden.<br />

AD: Träd 8


Binära träd<br />

Begreppet höjd av nod/träd<br />

Def: Ett träd är binärt om varje nod har högst två barn.<br />

Def: Ett träd är strikt binärt om varje nod har antingen<br />

noll eller två barn.<br />

AD: Träd 9<br />

Def: Höjden h(x) hos en nod x är antalet bågar på den längsta<br />

vägen ner till ett löv i det underträd i vilket x är rot.<br />

z<br />

x<br />

y<br />

u<br />

h(x) = 3<br />

h(y) = 1<br />

h(z) = 2<br />

h(u) = 0<br />

Def: Höjden hos ett träd T, h(T) = h(roten).<br />

Trädets höjd är viktig i värstafallsanalys av t <strong>ex</strong> operationer<br />

på binära sökträd.<br />

OBS: Många böcker använder en definition av höjden som<br />

avviker med 1 från vår: de räknar antalet bågar +1 på längsta<br />

vägen ned till löv.<br />

AD: Träd 10<br />

Begreppet djup för en nod<br />

Def: Djupet d(x) hos en nod x är antalet bågar på (den<br />

entydiga) vägen upp från x till roten.<br />

x<br />

z<br />

v<br />

y<br />

u<br />

d(x) = 0<br />

d(y) = 1<br />

d(z) = 1<br />

d(u) = 2<br />

d(v) = 3<br />

Anm: Ibland förekommer också begreppet nivå för en nod i<br />

ett träd. Roten har nivå 1. En godtycklig annan nod har nivå<br />

ett mer än sin förälder.<br />

Enkelt samband: nivå(x) = d(x) + 1<br />

AD: Träd 11<br />

Samband mellan antal noder och höjd<br />

hos binära träd<br />

För alla binära träd T med n noder gäller att<br />

h(T) ÿ n–1 (1)<br />

och<br />

h(T) 2<br />

log (n+1) –1 (2)<br />

(1) är enkelt att inse. Trädet får största möjliga höjd om man<br />

placerar en nod på varje nivå. Höjden blir då n–1.<br />

Ex för n= 3:<br />

AD: Träd 12


Samband mellan antal noder och höjd<br />

hos binära träd<br />

Minimal höjd för ett binärt träd<br />

(2):<br />

På djupet noll finns en nod (roten).<br />

På djupet ett högst 2 noder, på djupet 2 högst 4 noder etc.<br />

Allmänt finns det på djupet i högst 2 i noder.<br />

Det största djupet i ett träd med höjd h är h.<br />

==> n ÿ 1+2+4+...+2 i + ... + 2 h =2 h+1 –1.<br />

Vilket ger att h 2<br />

log (n+1) – 1<br />

AD: Träd 13<br />

Ett binärt träd med n noder har minimal höjd om<br />

n>2 h –1.<br />

Förklaring: Så många noder kan inte få plats i ett träd<br />

medlägrehöjdänh.(Ettträdmedhöjdenh–1<br />

rymmerjuhögst2 h – 1noder (se föregående bild).<br />

För binära träd med minimal höjd gäller därmed<br />

2<br />

log(n+1)–1ÿ h< 2 log(n+1)<br />

vilket ger att h log n.<br />

AD: Träd 14<br />

Binära träd med minimal höjd<br />

Lätt att se om trädet har minimal höjd. Kan man flytta några<br />

noder så att höjden blir mindre så har trädet inte minimal<br />

höjd.<br />

Minimal höjd<br />

Ej minimal höjd. Man kan<br />

flytta en nod och minska höjden<br />

AD: Träd 15<br />

Binära träd, representation<br />

Det implementationsförslag som gavs för generella träd med firstChild- och<br />

n<strong>ex</strong>tSibling-referenserinodernapassarintesåbraförbinäraträd.Idessavill<br />

man tillåta att en nod saknar vänster barn men har ett höger barn. I stället kan vi<br />

göra så här:<br />

class BinaryNode {<br />

BinaryNode left; // refererar till vänster barn<br />

BinaryNode right; // refererar till höger barn<br />

Object element;<br />

// nodens innehåll<br />

... // här infogas operationer<br />

element<br />

}<br />

class BinaryTree {<br />

private BinaryNode root;<br />

...// operationer på träd<br />

}<br />

left right<br />

AD: Träd 16


Binära träd, representation<br />

I det implementationsförslag som finns på förra bilden utnyttjas inte stödet<br />

för att skriva generiska klasser som finns i Java 5.0. Om man gör det blir det<br />

istället så här :<br />

class BinaryNode {<br />

BinaryNode left; // refererar till vänster barn<br />

BinaryNode right; // refererar till höger barn<br />

E element;<br />

// nodens innehåll<br />

... // här infogas operationer<br />

}<br />

class BinaryTree {<br />

private BinaryNode root;<br />

...// operationer på träd<br />

}<br />

element<br />

left right<br />

AD: Träd 17<br />

Binära träd och rekursion<br />

Trädens rekursiva definition gör att många operationer på<br />

träd enklast implementeras rekursivt.<br />

Ex: Skapa en kopia till ett binärt träd.<br />

Rekursiv metod:<br />

Skapa en kopia rootCopy av roten.<br />

rootCopy.left = kopia av vänster underträd<br />

rootCopy.right = kopia av höger underträd<br />

Basfall: ett tomt träd. Kopian är då null.<br />

AD: Träd 18<br />

Kopiera binärt träd, rekursiv utformning<br />

Kopiera binärt träd, rekursiv utformning<br />

/** Skapa en kopia av detta binära träd */<br />

public BinaryTree duplicate() {<br />

BinaryTree copy = new BinaryTree();<br />

if (root!=null) {<br />

copy.root = root.duplicate();<br />

}<br />

return copy;<br />

}<br />

Denna metod i klassen<br />

BinaryTree<br />

BinaryNode duplicate() {<br />

BinaryNode node = new BinaryNode(element);<br />

if (left!=null){<br />

node.left = left.duplicate();<br />

}<br />

if (right!=null){<br />

node.right = right.duplicate();<br />

}<br />

return node;<br />

}<br />

Denna metod i klassen<br />

BinaryNode<br />

AD: Träd 19<br />

AD: Träd 20


Kopiera binärt träd, alternativ rekursiv<br />

utformning<br />

public BinaryTree duplicate() {<br />

BinaryTree copy = new BinaryTree();<br />

copy.root = duplicate(root);<br />

return copy;<br />

}<br />

Båda dessa placeras i<br />

klassen BinaryTree. Den<br />

publika metoden är inte<br />

rekursiv, men är "driver"<br />

för den privata rekursiva<br />

metoden, där det huvudsakliga<br />

arbetet utförs.<br />

private BinaryNode duplicate(BinaryNode n) {<br />

if (n==null) {<br />

return null;<br />

} else {<br />

BinaryNode newNode = new BinaryNode(n.element);<br />

newNode.left = duplicate(n.left);<br />

newNode.right = duplicate(n.right);<br />

return newNode;<br />

}<br />

}<br />

AD: Träd 21<br />

Traversering av binära träd<br />

Att traversera ett träd betyder att man ”besöker” dess noder<br />

en efter en i någon ordning. I samband med besöket utför<br />

man någonting, beroende på vilket problem man håller på att<br />

lösa. Det finns flera sätt att traversera träd, bl a:<br />

• Nivå för nivå med början på rotens nivå. På varje nivå<br />

besöks noderna i ordning från vänster till höger. Brukar<br />

kallas level-by-level, top-down.<br />

• Nivå för nivå med början på den nivå som ligger längst bort<br />

från roten. På varje nivå besöks noderna i ordning från<br />

vänster till höger. Brukar kallas level-by-level, bottom-up<br />

AD: Träd 22<br />

Rekursivt definierade traverseringar<br />

Det finns också tre rekursivt definierade sätt att besöka alla<br />

noder i ett binärt träd:<br />

Rekursivt definierade traverseringar, <strong>ex</strong><br />

M<br />

Preorder:<br />

Inorder:<br />

Postorder:<br />

Först roten<br />

Sedan vänster underträd i preorder<br />

Sedan höger underträd i preorder<br />

Först vänster underträd i inorder<br />

Sedan roten<br />

Sedan höger underträd i inorder<br />

Först vänster underträd i postorder<br />

Sedan höger underträd i postorder<br />

Sedan roten<br />

AD: Träd 23<br />

H<br />

S<br />

A K N T<br />

Preorder: M,H,A,K,S,N,T<br />

Inorder: A, H, K, M, N, S, T<br />

Postorder: A, K, H, N, T, S, M<br />

AD: Träd 24


Implementation av rekursiva<br />

traverseringar<br />

Implementation av rekursiva<br />

traverseringar<br />

Skiss av hur genomgång av ett binärt träd i preorder kan<br />

implementeras i Java. (Vi förutsätter n!=null vid anrop):<br />

public void preOrder(BinaryNode n) {<br />

// här infogas satser som utför det som skall<br />

// göras då noden n besöks<br />

if (n.left!=null) {<br />

preOrder(n.left); // (*) se nästa bild<br />

}<br />

if (n.right!=null) {<br />

preOrder(n.right); // (**) se nästa bild<br />

}<br />

}<br />

AD: Träd 25<br />

• Vid inorder-genomgång placeras satserna som<br />

”behandlar” noden i stället efter den sats som<br />

kommenterats med (*) ovan.<br />

• Vid postorder-genomgång placeras de efter satsen som<br />

kommenterats med (**).<br />

Samtidigt byter man naturligtvis namn på metoden.<br />

AD: Träd 26<br />

Traversering av träd<br />

Många algoritmer för träd kan tolkas som traversering i en<br />

viss ordning:<br />

• Evaluering av aritmetiskt uttryck. Underträdens värden<br />

måste först evalueras. Alltså postorder.<br />

• Skriva ut innehållet i ett binärt sökträd i växande ordning.<br />

Blir inorder.<br />

• Metoden duplicate är <strong>ex</strong>empel på preordertraversering.<br />

Först skapas kopia på noden där vi befinner oss, sedan på<br />

dess underträd.<br />

Ibland saknar ordningen betydelse. Skall vi t <strong>ex</strong> räkna antalet<br />

noder kan vi besöka dem i vilken ordning som helst, bara alla<br />

blir besökta (räknade) precis en gång.<br />

AD: Träd 27<br />

Traversering av träd<br />

Problem med implementationen i Java på tidigare bild:<br />

• Satser för att ”behandla” den nod som besöks måste<br />

infogas i traverseringsmetoden och kan inte varieras av<br />

den som anropar metoden. Den som implementerar en<br />

ADT för binära träd kan inte förutse vad olika användare<br />

kan tänkas önska att ”behandla” skall innebära.<br />

• Det finns inget sätt att avbryta traverseringen innan alla<br />

noder besöks. Det kan behövas om t <strong>ex</strong> ”behandla”<br />

innebär att man söker efter nod med visst innehåll.<br />

AD: Träd 28


Traversering/iteratorer<br />

Traversering/iteratorer<br />

För att komma tillrätta med dessa problem är det önskvärt att<br />

ha s.k. <strong>ex</strong>terna iteratorer för träd. Vi vill alltså ha något som<br />

motsvarar iteratorer för listor, d.v.s. en klass med operationer<br />

för att ”hämta nästa element” och undersöka ”om det finns<br />

fler element”.<br />

För binära träd vill man helst ha flera olika implementationer<br />

av ett sådant interface. En där ”nästa” betyder nästa element<br />

vid en preordergenomgång, en där ”nästa” betyder nästa vid<br />

en inordergenomgång etc.<br />

I läroboken finns i avsnitt 18.4 beskrivet hur iteratorer på<br />

binära träd kan implementeras (med hjälp av stack).<br />

Kan läsas kursivt.<br />

När vi senare går igenom sökträd kommer vi att titta på<br />

vilket stöd för iteration det finns i Javas klasser (TreeSet<br />

och TreeMap).<br />

AD: Träd 29<br />

AD: Träd 30<br />

Nivå- för nivåtraversering<br />

Kan implementeras med hjälp av en kö:<br />

Skapa en tom kö;<br />

Lägg in rotnoden i kön;<br />

Så länge kön inte är tom<br />

Tag ut och behandla första noden (actNode);<br />

Om actNode.left!=null<br />

Lägg in actNode.left i kön;<br />

Om actNode.right!=null<br />

Lägg in actNode.right i kön;<br />

Kan generaliseras till andra trädtyper än binära. Man lägger<br />

då in alla barnen (från vänster till höger) i kön.<br />

AD: Träd 31<br />

Ex. på användning av träd som abstrakt<br />

modell: Filkomprimering<br />

Representera n olika symboler med binära strängar av lika längd L.<br />

Detkrävsdåatt2 L n. Alltså blir minsta möjliga L log n.<br />

Ex:Påenfilförekommerendast8olikatecken:c,d,e,f,k,l,u,z.<br />

L = 3 räcker alltså.<br />

Tecken<br />

Antal<br />

Kod (t <strong>ex</strong>)<br />

Antal bitar<br />

c<br />

32<br />

000<br />

96<br />

d<br />

42<br />

001<br />

126<br />

e<br />

120<br />

010<br />

360<br />

f<br />

24<br />

011<br />

72<br />

k<br />

7<br />

100<br />

21<br />

42<br />

101<br />

126<br />

37<br />

110<br />

111<br />

Totalt antal bitar: 918. Jfr vanlig ASCII: 306 tecken * 8<br />

bitar = 2448 bitar. Men det går att göra bättre.<br />

z<br />

2<br />

111<br />

6<br />

AD: Träd 32<br />

l<br />

u


Filkomprimering<br />

Det går att komprimera filen bättre om man tillåter olika<br />

längd på kodorden.<br />

Idé: Korta kodord för frekventa tecken och längre för<br />

mindre frekventa.<br />

Villkor: Kodningen måste vara sådan att mottagaren kan<br />

avkoda mottagen sträng entydigt med kännedom<br />

om hur de enskilda symbolerna översätts.<br />

Ex: Treteckena,b,c.Kodas0,1respektive01<br />

Mottagen kodad sträng: 001.<br />

Betyder aab eller ac<br />

Prefix-kodning:<br />

Filkomprimering<br />

Ingen symbol kodas med en sträng som<br />

utgör prefix till en annan symbols kodsträng.<br />

Prefix-kodning => Entydig avkodning möjlig.<br />

Mål:<br />

För en given mängd symboler S och en<br />

sträng av symboler ur S, finn en kodning<br />

som uppfyller prefixvillkoret och som<br />

minimerar längden på den kodade strängen.<br />

AD: Träd 33<br />

AD: Träd 34<br />

Filkomprimering, prefixkodning<br />

Filkomprimering<br />

Prefix-kodning kan illustreras av ett binärt träd, <strong>ex</strong> (koden<br />

på bild 31):<br />

0<br />

1<br />

0<br />

0 1 0 1 0 1 0 1<br />

c d e f k l u z<br />

1<br />

0 1<br />

Vänstergren motsvarar nolla i kodordet, högergren etta.<br />

De tecken som kodas motsvarar löv. Väg från roten till<br />

ett visst löv är kodordet för symbolen i lövet.<br />

AD: Träd 35<br />

Antag att symbolen x finns w(x) gånger i strängen som skall<br />

kodas.<br />

Längden på kodordet för x är d(x), (d(x)= djupet för det löv<br />

som motsvarar symbolen x i kodträdet).<br />

Totala längden för den kodade strängen L =<br />

summationen görs över alla löv x i kodträdet.<br />

Vi söker den prefixkodning som minimerar L.<br />

w(x)*d(x), där<br />

AD: Träd 36


Huffmans algoritm för optimal<br />

prefixkodning<br />

Börja med en serie träd, vardera bestående av ett enda löv.<br />

Till varje träd associeras en av symbolerna som skall kodas<br />

och en vikt = symbolens frekvens:<br />

z<br />

2<br />

k<br />

7<br />

f c u d l e<br />

24 32 37 42 42 120<br />

Välj de två träd som har minst vikt. Bygg ihop dem till ett<br />

träd, genom att låta dem bli vänster- respektive högerbarn<br />

till en ny nod. Till det nya trädet associeras en vikt =<br />

summan av de sammanslagna delträdens vikter.<br />

Huffmans algoritm för optimal<br />

prefixkodning<br />

z<br />

2<br />

9 f<br />

24<br />

k<br />

7<br />

c u d l e<br />

32 37 42 42 120<br />

Fortsätt på samma sätt att slå samman två träd med minsta<br />

vikt:<br />

c<br />

32<br />

z<br />

2<br />

33<br />

9 f<br />

24<br />

k<br />

7<br />

u d l e<br />

37 42 42 120<br />

AD: Träd 37<br />

AD: Träd 38<br />

Huffmans algoritm för optimal<br />

prefixkodning<br />

u d<br />

37 42<br />

l<br />

42<br />

c<br />

32<br />

z<br />

2<br />

65<br />

33<br />

9 f<br />

24<br />

k<br />

7<br />

… Efter ytterligare fyra steg får man ….<br />

e<br />

120<br />

e<br />

120<br />

Huffmans algoritm för optimal<br />

prefixkodning<br />

306<br />

0 1<br />

0<br />

0<br />

79<br />

1<br />

u d<br />

37 42<br />

186<br />

l<br />

42<br />

1<br />

0<br />

107<br />

0<br />

c<br />

32<br />

0<br />

z<br />

2<br />

1<br />

65<br />

1<br />

33<br />

0 1<br />

9 f<br />

1<br />

24<br />

k<br />

7<br />

Kodtabell:<br />

c 32 1110 128<br />

d 42 101 126<br />

e 120 0 120<br />

f 24 11111 120<br />

k 7 111101 42<br />

l 42 110 126<br />

u 37 100 111<br />

z 2 111100 12<br />

Σ = 785<br />

AD: Träd 39<br />

AD: Träd 40


ADT l<strong>ex</strong>ikon (map, dictionary,<br />

symboltabell)<br />

ADT l<strong>ex</strong>ikon<br />

Ofta behöver man i datorprogram kunna hantera en mängd<br />

element för vilka åtminstone en jämförelseoperation för<br />

likhet är definierad. Jämförelse baseras oftast på en<br />

”nyckel” som ingår i elementet.<br />

Ex. elementen består av namn och telefonnummer.<br />

Nyckeln är namnet.<br />

Elementen i samlingen betraktas ofta som bestående av två<br />

delar nyckeln och tillhörande värde, där jämförelse görs<br />

med avseende på nyckeln.<br />

Åtminstone följande tre operationer skall vara möjliga att<br />

utföra:<br />

• Sökning. Givet en nyckel, sök tillhörande värde.<br />

• Insättning. Dubbletter tillåts vanligen inte.<br />

• Borttagning.<br />

En abstrakt datatyp med dessa operationer brukar kallas<br />

l<strong>ex</strong>ikon eller symboltabell.<br />

AD: Träd 41<br />

AD: Träd 42<br />

Implementation av L<strong>ex</strong>ikon<br />

Effektiv implementation av l<strong>ex</strong>ikon<br />

Implementationsförslag:<br />

Lista (länkad implementation)<br />

Om listan hålls sorterad efter växande nycklar blir<br />

alla operationer O(n).<br />

Om listan är osorterad blir insättning O(1) men de<br />

övriga O(n).<br />

Vektor<br />

Om den hålls sorterad blir sökningen O(log n)<br />

(binärsökning).<br />

Men insättning och borttagning blir O(n), ty man<br />

måste flytta element i vektorn.<br />

AD: Träd 43<br />

Genom att använda binärt sökträd, får vi en implementation<br />

av Map/L<strong>ex</strong>ikon som förenar de goda egenskaperna hos<br />

vektorimplementationen (snabb sökning) med det som är bra<br />

med listimplementationen (snabb insättning, borttagning när<br />

man har sökt upp rätt position).<br />

Ett binärt sökträd är ett binärt träd där elementen är par<br />

(nyckel, värde) och där nycklar i vänster underträd alltid är<br />

mindre än rotens nyckel som i sin tur är mindre än alla<br />

nycklar i höger underträd. Observera att detta skall gälla för<br />

alla underträd i trädet.<br />

AD: Träd 44


Binära sökträd<br />

Binära sökträd, <strong>ex</strong>empel<br />

Ett binärt sökträd är ett binärt träd som i varje nod lagrar ett<br />

element innehållande en nyckel och ett värde. För varje<br />

nod x i trädet gäller:<br />

x<br />

Elementen i noderna i <strong>ex</strong>emplet nedan innehåller en sträng<br />

på vilken jämförelsen baseras. Ett element e 1 anses vara<br />

mindre än e 2 om strängen i e 1 kommer alfabetiskt före<br />

strängen i e 2 .<br />

Mona<br />

Hans<br />

Svea<br />

T L<br />

T R<br />

elementen i T L < elementet i x < elementen i T R<br />

AD: Träd 46<br />

Anna<br />

Karl<br />

Nora<br />

Tora<br />

AD: Träd 45<br />

Anna<br />

Hans<br />

Karl<br />

Mona<br />

Binära sökträd, sökning<br />

Lyckad sökning:<br />

Sök efter Nora.<br />

Börja i roten.<br />

Om likhet, stanna.<br />

Vik av åt vänster om det sökta<br />

är mindre annars åt höger.<br />

Pilarna anger sökvägen.<br />

Nora<br />

Svea<br />

Tora<br />

Misslyckad sökning:<br />

Sök efter Lena.<br />

Pilarna anger sökvägen.<br />

Slutar när ingen nod finns i<br />

den riktning man skall vika<br />

av.<br />

Anna<br />

Hans<br />

Karl<br />

Mona<br />

Lägg märke till likheten med binärsökning i vektor!<br />

Nora<br />

Svea<br />

Tora<br />

AD: Träd 47<br />

Binära sökträd, insättning<br />

Den nya noden sätts in som löv. Rätt plats letas upp<br />

som i sökningsoperationen. Man kan därför samtidigt<br />

kontrollera att nyckeln inte redan finns i trädet.<br />

Insättning misslyckad sökning.<br />

Ex: Sätt in Lena:<br />

Anna<br />

Hans<br />

Karl<br />

Mona<br />

Lena<br />

Nora<br />

Svea<br />

Tora<br />

AD: Träd 48


Binära sökträd, borttagning<br />

Leta först upp nyckeln som skall bort samt förälder (sökning).<br />

Trädet skall efter borttagningen fortfarande vara ett binärt<br />

sökträd!<br />

Enklaste fallet: Den nod som skall bort är ett löv. Ex ta bort<br />

nyckeln 1 ur trädet:<br />

4<br />

8<br />

11<br />

4<br />

8<br />

11<br />

Binära sökträd, borttagning<br />

Näst enklaste fallet: Den nod x som skall tas bort har bara ett<br />

barn. T <strong>ex</strong> bara vänster barn. Ex: ta bort nyckeln 2:<br />

förälder<br />

4<br />

8<br />

11<br />

förälder<br />

x 2 6 9 13<br />

1 6 9 13<br />

4<br />

8<br />

11<br />

1<br />

2<br />

5<br />

6<br />

7<br />

9<br />

13<br />

2 6 9<br />

Regel: Låt den ref i föräldern som refererar till noden som skall<br />

bort bli null. Spec.fall: förälder saknas. => roten är ett löv =><br />

5<br />

7<br />

13<br />

Sätt roten = null. AD: Träd 49<br />

AD: Träd 50<br />

1<br />

5<br />

7<br />

Regel: Låt den ref i förälder som refererar till x i stället<br />

referera till barnet till x.<br />

Specialfall: förälder==null => x==roten. Sätt roten =<br />

barnet till x.<br />

5<br />

7<br />

Binära sökträd, borttagning<br />

Binära sökträd, borttagning<br />

Svåraste fallet: Noden som skall tas bort har två barn. Ex: ta<br />

bort 8 ur trädet:<br />

x<br />

x<br />

1<br />

2<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

11<br />

13<br />

2<br />

10 1 5 7 10<br />

4<br />

6<br />

8<br />

Steg 1: Leta upp minsta noden m i<br />

x:s högra underträd. Forts …<br />

9<br />

11<br />

13<br />

m<br />

1<br />

2<br />

x<br />

4<br />

6<br />

5 7<br />

9<br />

9<br />

11<br />

10<br />

13<br />

Steg 2: Flytta innehållet<br />

i m till x.<br />

m<br />

1<br />

2<br />

x<br />

4<br />

6<br />

5 7<br />

9<br />

10<br />

11<br />

Steg 3: Tag bort m (som<br />

har högst ett barn)<br />

13<br />

AD: Träd 51<br />

AD: Träd 52


Binära sökträd<br />

Trädets form beror på insättningsordningen.<br />

Ex: Sätt in 1, 2, .., 7 Insättningsordningen<br />

( i den ordningen) 4, 2, 1, 6, 5, 3, 7 ger:<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

4<br />

2 6<br />

1 3 5 7<br />

Ordning:<br />

2, 5, 1, 6, 7, 3, 4<br />

AD: Träd 53<br />

1<br />

2<br />

3<br />

5<br />

4<br />

6<br />

7<br />

Analys av operationer på BST, värstafall<br />

Lyckad sökning<br />

• Kostnaden är proportionell mot antal noder på vägen<br />

ner till sökt nod x, dvs. d(x)+1.<br />

• I värsta fall söker vi det löv som ligger längst bort från<br />

roten ==> i värsta fall = h(T)+1.<br />

• I ett träd med n noder kan höjden maximalt bli n–1.<br />

==> i värsta fall kostar sökning O(n).<br />

• Minsta värde på höjden är log n. Värstafallstiden för<br />

sökning i träd med minimal höjd är alltså O(log n)<br />

Misslyckad sökning<br />

• Antalet noder på den gren vi söker kan maximalt vara<br />

n. Därför blir värstafallet O(n)<br />

AD: Träd 54<br />

Analys av operationer på BST, värstafall<br />

Insättning<br />

• Vi söker oss ner på en gren i trädet och sätter in det nya elementet som<br />

ett löv. Det blir samma kostnad som för misslyckad sökning dvs. O(n) i<br />

värsta fall.<br />

• Om elementet redan finns avbryts operationen. Men i värsta fall hittar<br />

man elementet i det mest avlägsna lövet. Därför O(n) även i detta fall.<br />

Borttagning<br />

• Består av två sökningar i värsta fall: Först efter noden som skall tas bort,<br />

sedan efter minsta noden i dess högra underträd.<br />

• Sökningarna tillsammans kan inte kosta mer än längden på den<br />

längsta grenen i trädet dvs. O(n)<br />

• Om det som skall tas bort inte finns blir det en misslyckad sökning dvs.<br />

O(n) i värsta fall även här.<br />

Analys av operationer på BST, medelfall<br />

Den interna väglängden för ett träd T, IPL(T), definieras<br />

som summan av djupet av alla noder i trädet.<br />

Om vi dividerar IPL(T) med antalet noder i trädet, n, får vi<br />

”medeldjupet” för en nod i T.<br />

En lyckad sökning efter en nod x kostar d(x)+1.<br />

En lyckad sökning i trädet kostar alltså i medeltal<br />

IPL(T)/n +1.<br />

Önskvärt: Träd med så liten intern väglängd som möjligt.<br />

AD: Träd 55<br />

AD: Träd 56


Analys av operationer på BST<br />

Man kan visa att för fixt n, så är det minimala värdet på den<br />

interna väglängden för ett binärt träd n*log n. Dessa träd är<br />

enkla att känna igen: De har så fullt av noder som det är möjligt<br />

på varje nivå utom möjligtvis på nivån längs bort från roten.<br />

Analys av operationer på BST<br />

Det maximala värdet av den interna väglängden är<br />

n(n–1)/2 och inträffar för träd med maximal höjd (dvs<br />

träd med bara en nod på varje nivå).<br />

Ex:<br />

Träd med minimal<br />

intern väglängd<br />

Detta träd har inte minimal<br />

intern väglängd<br />

AD: Träd 57<br />

AD: Träd 58<br />

Analys av operationer på BST, medelfall<br />

Analys av operationer på BST, medelfall<br />

Man kan visa att medelvärdet för den interna väglängden för<br />

alla träd med n noder är 1.38n*log n under förutsättning<br />

att alla insättningsordningar för de n nycklarna i trädet är<br />

lika sannolika.<br />

Kostnaden för att söka efter en nod x som finns i trädet är<br />

d(x)+1. Medelkostnaden för att söka någon av de n noderna<br />

är alltså<br />

( (d(x)+1))/n = 1 + IPL(T)/n<br />

Medelkostnaden för en lyckad sökning i ett binärt sökträd är<br />

därför under de givna förutsättningarna:<br />

1 + (1.38 n log n)/n = 1 + 1.38 log n = O(log n).<br />

AD: Träd 59<br />

Insättning = misslyckad sökning. Insättningsplatsen är vid<br />

någon av null-referenserna nere i trädet. Ritade med fyrkant<br />

i figuren:<br />

De noder som ritats ut som kvadrater ovan brukar kallas <strong>ex</strong>terna<br />

noder i trädet. Man får alltså fram dem genom att komplettera<br />

trädet så att alla de vanliga noderna i trädet har två barn.<br />

Kostnaden för en insättning är d(x)+1 där x är den <strong>ex</strong>terna nod<br />

vid vilken insättningen sker.<br />

AD: Träd 60


Analys av operationer på BST, medelfall<br />

Analys av operationer på BST, medelfall<br />

Den <strong>ex</strong>terna väglängden i ett träd T, EPL(T) definieras som<br />

summan av djupet av alla de <strong>ex</strong>terna noderna i T.<br />

Enkla samband som kan visas:<br />

• Det finns n+1 <strong>ex</strong>terna noder i ett träd med n noder<br />

• EPL(T) = IPL(T) + 2n<br />

Medelkostnaden för en insättning blir<br />

( (d(x)+1))/(n+1) = ( d(x))/(n+1) + ( 1 )/(n+1) =<br />

= EPL(T)/(n+1) + 1 =<br />

= (IPL(T) + 2n)/(n+1) +1<br />

över alla<br />

de n+1<br />

<strong>ex</strong>terna<br />

noderna<br />

Dvs i medeltal (1.38n log n + 2n)/(n+1) + 1 = O(log n)<br />

AD: Träd 61<br />

AD: Träd 62<br />

Analys av operationer på BST, medelfall<br />

Om vi gör borttagningar i trädet är det inte längre klart att<br />

alla trädformer blir lika sannolika.<br />

Vid borttagning av nod med två barn gick vi t <strong>ex</strong> alltid ner i<br />

höger underträd och tog bort en nod där.<br />

Empiriska studier visar att det är rimligt att anta att när man<br />

gör många slumpmässiga insättningar och borttagningar i ett<br />

binärt sökträd får trädet sådan form att medelfallet för alla<br />

operationerna är O(log n).<br />

Men: värstafallet är O(n). Önskvärt: att i samband med<br />

insättning och borttagning kunna se till att trädformen förblir<br />

sådan att även värstafallet blir O(log n).<br />

AD: Träd 63<br />

Balanserade binära sökträd<br />

Mål: Se till att ett binärt sökträd får en sådan form att höjden<br />

är O(log n) oavsett i vilken ordning insättningar och<br />

borttagningar görs.<br />

Det finns inga tillräckligt effektiva algoritmer för att i<br />

samband med insättning/borttagning se till att trädet får<br />

minimal intern väglängd, vilket skulle garantera att höjden<br />

alltid förblir log n<br />

Det finns svagare krav än minimal intern väglängd som<br />

garanterar att höjden blir O(log n) och för vilka det finns<br />

tillräckligt effektiva algoritmer.<br />

AD: Träd 64


Balanserade binära sökträd, AVL-träd<br />

Balanserade binära sökträd, AVL-träd<br />

Ett sådant villkor ställdes upp av Adelson-Velskii och Landis<br />

och träd som uppfyller villkoret kallas därför ofta AVL-träd<br />

(eller bara balanserade träd/ höjdbalanserade träd).<br />

Def:Ettbinärtträdärbalanserat om det för varje nod i trädet<br />

gäller att höjdskillnaden mellan dess båda underträd är högst<br />

ett.<br />

AD: Träd 65<br />

Balanserat (men har inte minimal<br />

intern väglängd)<br />

Ej balanserat<br />

Anm: Träd med minimal intern väglängd (vilka har höjden<br />

log n) är alltid balanserade. Omvändningen gäller ej (se fig<br />

till vänster ovan). Det är alltså ett svagare villkor för ett träd<br />

att vara balanserat än att ha minimal intern väglängd.<br />

AD: Träd 66<br />

Balanserade binära sökträd, AVL-träd<br />

Balanserade binära sökträd, AVL-träd<br />

Man kan visa att:<br />

• I ett balanserat träd med n noder är höjden h ≤ 1.44 * 2 log n<br />

• Det finns algoritmer med värstafallstid O( 2 log n) som i<br />

samband med insättning av en ny nod eller borttagning av en<br />

nod i ett balanserat träd ser till att trädet förblir balanserat.<br />

Algoritmerna för att balansera ett träd kräver att information om<br />

höjdförhållandena mellan vänster och höger underträd lagras i<br />

varje nod. T <strong>ex</strong> lagras -1 om vänster underträd har en höjd som<br />

är ett större än höger underträd, 0 om de har lika höjd och +1 om<br />

höger underträd har en höjd som är ett större än vänster<br />

underträd.<br />

AD: Träd 67<br />

Balanseringsalgoritmerna arbetar med rotationer i trädet:<br />

x<br />

y<br />

Enkel högerrotation<br />

vid y A<br />

x<br />

y<br />

C<br />

A B<br />

B C<br />

y Enkel vänsterrotation<br />

vid y y<br />

x<br />

x<br />

A<br />

C<br />

B C<br />

A B<br />

AD: Träd 68


Balanserade binära sökträd, AVL-träd<br />

Balanserade binära sökträd, AVL-träd<br />

A<br />

y<br />

z<br />

x<br />

D<br />

Höger-vänsterdubbelrotation<br />

z<br />

y<br />

A B C<br />

x<br />

D<br />

• Obalans som orsakas av en insättning (i ett balanserat<br />

träd) kan alltid repareras genom högst en enkelrotation<br />

eller en dubbelrotation i någon av noderna på vägen från<br />

den nya noden till roten.<br />

B<br />

x<br />

A<br />

B<br />

C<br />

y<br />

z D<br />

C<br />

Vänster-högerdubbelrotation<br />

z<br />

x<br />

A B C<br />

y<br />

D<br />

• Obalans som orsakas av en borttagning (i ett balanserat<br />

träd) kan repareras genom enkla eller dubbla rotationer i<br />

noderna på vägen från den borttagna noden till roten. Här<br />

kan det krävas rotation i varje nod på vägen.<br />

AD: Träd 69<br />

AD: Träd 70<br />

Balanserade binära sökträd, AVL-träd<br />

Balansering av binära sökträd, <strong>ex</strong><br />

Tidskompl<strong>ex</strong>itet:<br />

• Vi utgår från ett balanserat träd före<br />

insättningen/borttagningendvsdesshöjdh<br />

• Varje rotation tar konstant tid<br />

O(log n).<br />

Därmed klart att algoritmerna för insättning/borttagning<br />

kombinerade med eventuell balansering blir O( 2 log n)<br />

Ex: Sätt in nycklarna 1, 2, 3, ..., 7 i ett från början tomt<br />

AVL-träd.<br />

Insatt: 1 2 3<br />

1 1<br />

2<br />

1<br />

y<br />

2<br />

x<br />

3<br />

Obalans vid y<br />

Efter enkel vänsterrotation<br />

vid y:<br />

1<br />

2<br />

forts. ....<br />

3<br />

AD: Träd 71<br />

AD: Träd 72


Balansering av binära sökträd, <strong>ex</strong><br />

Balansering av binära sökträd, <strong>ex</strong><br />

Insatt: 4 5<br />

2<br />

2<br />

1 3<br />

1 3<br />

4<br />

y<br />

4<br />

x<br />

5<br />

Obalans vid y<br />

Efter enkel vänsterrotation<br />

vid y:<br />

2<br />

1 4<br />

3 5<br />

forts …<br />

Insatt: 6<br />

2<br />

1<br />

3<br />

y<br />

4<br />

x<br />

5<br />

6<br />

Obalans vid y<br />

Efter enkel vänsterrotation<br />

vid y:<br />

1<br />

2<br />

4<br />

3<br />

5<br />

6<br />

forts …<br />

AD: Träd 73<br />

AD: Träd 74<br />

Balansering av binära sökträd, <strong>ex</strong><br />

Balansering av binära sökträd, <strong>ex</strong><br />

Insatt: 7<br />

4<br />

2 5<br />

1 3<br />

y<br />

6<br />

x<br />

7<br />

Obalans vid y<br />

Efter enkel vänsterrotation<br />

vid y:<br />

4<br />

2<br />

1 3 5<br />

6<br />

AD: Träd 75<br />

7<br />

Efter insättning av 15 och 14 (i den ordningen) i det sista<br />

trädet på föregående bild får man trädet:<br />

4<br />

2<br />

1 3 5<br />

Det råder nu obalans vid y<br />

men om man försöker med<br />

en enkel vänsterrotation<br />

blir det<br />

6<br />

y<br />

7<br />

14<br />

15<br />

x<br />

4<br />

2 6<br />

1 3 5 15<br />

7<br />

14<br />

Detta träd är fortfarande<br />

obalanserat! Om man i stället gör en<br />

dubbel höger-vänsterrotation<br />

vid y blir det som på nästa bild.<br />

AD: Träd 76


Balansering av binära sökträd, <strong>ex</strong><br />

Balansering av binära sökträd<br />

2<br />

4<br />

1 3 5<br />

6<br />

y<br />

7<br />

15<br />

x<br />

Efter dubbel högervänsterrotation<br />

vid y:<br />

Enkla rotationer räcker i samband med insättning när<br />

obalansen har någon av formerna:<br />

14<br />

z<br />

2<br />

4<br />

1 3 5<br />

6<br />

14<br />

Dubbla rotationer behövs däremot vid obalans av typerna:<br />

7<br />

15<br />

AD: Träd 77<br />

AD: Träd 78<br />

Klassen BinarySearchTree<br />

Exempel på en klass som beskriver ett binärt sökträd:<br />

public class BinarySearchTree> {<br />

public BinarySearchTree() {...}<br />

public void insert(E x) {...}<br />

public void remove(E x) {...}<br />

public E find(E x) {...}<br />

public boolean isEmpty() {...}<br />

}<br />

AD: Träd 79<br />

Ex på användning av BinarySearchTree<br />

Antag vi vill sätta in personuppgifter (namn och telefonnummer)<br />

i ett BST och vi vill hålla det sorterat efter namn.<br />

Vi inför då en klass Person:<br />

class Person implements Comparable {<br />

private String name;<br />

private String address;<br />

public Person(String n, String addr) {...}<br />

public int compareTo(Person rhs) {<br />

return name.compareTo(rhs.name);<br />

}<br />

public boolean equals(Object rhs) {<br />

return compareTo((Person) rhs) == 0;<br />

}<br />

public int getAddress() {...}<br />

}<br />

AD: Träd 80


Ex på användning av BinarySearchTree<br />

Vi kan nu sätta in personer i ett binärt sökträd:<br />

BinarySearchTree reg = new BinarySearchTree();<br />

reg.insert(new Person(”Adam”, ”Paradisgatan 1”));<br />

reg.insert(new Person(”Eva”, ”Paradisgatan 1”));<br />

...<br />

Person p = reg.find(new Person(”Adam”, ””));<br />

if (p != null) {<br />

System.out.println(”Adam´s address: ” + p.getAddress());<br />

} else {<br />

...<br />

AD: Träd 81<br />

ADTn L<strong>ex</strong>ikon<br />

I en ADT L<strong>ex</strong>ikon (Dictionary) skall det finnas följande<br />

operationer:<br />

• Givet nyckel sök upp tillhörande värde<br />

• Sätt in ny nyckel med tillhörande värde<br />

• Ta bort element med viss nyckel<br />

För att söka och för att undersöka att det inte finns dubbletter<br />

vid insättning måste man kräva att nycklarna är av en typ för<br />

vilken jämförelse avseende likhet är definierad. Detta<br />

uppfyller alla objekt i Java (equals).<br />

AD: Träd 82<br />

Interfacet Map i Java<br />

I Java finns interfacet Map som har de operationer som<br />

krävs för ett l<strong>ex</strong>ikon:<br />

public interface Map {<br />

boolean containsKey(Object key);<br />

V get(Object key);<br />

V put(K key, V value);<br />

V remove(Object key);<br />

int size();<br />

...// ytterligare operationer för att bl a<br />

...// ta reda på alla värden i samlingen etc.<br />

}<br />

AD: Träd 83<br />

Användning av klass som implementerar<br />

interfacet Map<br />

Antag vi har en klass som implementerar interfacet Map:<br />

class TreeMap implements Map {...}<br />

Ex på användning:<br />

TreeMap aMap =<br />

new TreeMap();<br />

aMap.put(”Kalle”, new Integer(12345));<br />

aMap.put(”Hobbe”, new Integer(6789));<br />

...<br />

Integer phoneNbr = aMap.get(”Kalle”);<br />

if (phoneNbr != null) {<br />

System.out.println(”Kalle has phone number: ” +<br />

phoneNbr.intValue());<br />

AD: Träd 84<br />

}


Javas klasser TreeSet och TreeMap<br />

Javas klasser TreeSet och TreeMap<br />

I java.util finns följande klasser som implementerats med hjälp<br />

av träd och som garanterar O(log n) kompl<strong>ex</strong>itet i värsta fall för<br />

sökning, insättning och borttagning:<br />

TreeSet: En klass med ett Set-interface (add, contains,<br />

remove)<br />

TreeMap: En klass med ett Map-interface (get, put, remove)<br />

Båda har implementerats med ett slags balanserade träd,<br />

dock inte AVL-träd utan s.k. röd-svarta träd.<br />

Objekten som sätts in i en TreeSet och nycklarna som sätts<br />

in i en TreeMap måste antingen implementera interfacet<br />

Comparable eller så måste man ha tillhandahållit ett s.k.<br />

Comparator-objekt när trädet konstruerats. Annars får man<br />

<strong>ex</strong>ekveringsfel.<br />

Mer om detta på seminarium 4.<br />

AD: Träd 85<br />

AD: Träd 86<br />

Ordningsstatistik<br />

Ordningsstatistik<br />

Problem: Givet en samling element för vilka jämförelse är<br />

definierad. Tag reda på det i storleksordning k:e elementet i<br />

samlingen.<br />

Om k = 1 söker vi alltså det minsta elementet, om k = 2 söker<br />

vi det näst minsta etc.<br />

Om elementen finns insatta i ett BST kan vi lösa problemet<br />

genom att traversera trädet i inorder och stanna efter k steg.<br />

Tidskompl<strong>ex</strong>iteten beror då på k.<br />

AD: Träd 87<br />

Det går att lösa problemet effektivare genom att använda ett<br />

BST där man ser till att noderna i trädet håller reda på sitt<br />

underträds storlek:<br />

class BinaryNodeWithSize <strong>ex</strong>tends BinaryNode {<br />

int size; // Antalet noder i underträdet med<br />

// denna nod som rot, inklusive noden själv.<br />

BinaryNodeWithSize(E x) {<br />

super(x);<br />

size = 0; // Kommer att ökas till 1 i samband<br />

// med att noden sätts in som ett<br />

// löv i trädet och sedan uppdateras<br />

// i samband med ytterligare<br />

// insättningar/borttagningar i trädet<br />

}<br />

AD: Träd 88<br />

}


Ordningsstatistik<br />

class BinarySearchTreeWithRank> <strong>ex</strong>tends BinarySearchTree {<br />

/** Sök upp det element som är nr k i storleksordning */<br />

public E findKth(int k);<br />

// OBSERVERA att även insert och remove måste omdefinieras<br />

// i denna klass eftersom size-attribut i vissa noder<br />

// behöver uppdateras. Se boken för detaljer.<br />

}<br />

Ex. på ett träd av typ BinarySearchTreeWithRank där enbart<br />

size-attributen i noderna visas: 6<br />

3 2<br />

1 1 1<br />

AD: Träd 89<br />

Ordningsstatistik<br />

Implementationsidé för findKth (med tidskompl<strong>ex</strong>itet som nu<br />

beror enbart på höjden av trädet):<br />

Sök rekursivt med början i roten. Vi får tre fall beroende på<br />

storleken S L (size-attributet ) hos vänster underträd:<br />

•Omk=S L + 1 är vi klara<br />

•OmkS L + 1 så sök det element som är (k – S L –1) i<br />

storleksordning i höger underträd<br />

AD: Träd 90<br />

Ordningsstatistik<br />

Ex: Sök 4:e nyckeln i storleksordning i trädet nedan.<br />

Första talet i en nod anger nyckel, andra talet anger size.<br />

findKth(4)<br />

findKth(4)<br />

2<br />

1<br />

7<br />

5<br />

8<br />

1<br />

9<br />

3<br />

11<br />

7<br />

10<br />

1<br />

12<br />

1<br />

findKth(2). Klart! k = S L<br />

+1<br />

AD: Träd 91<br />

Generaliserade sökträd<br />

Ett m-vägs sökträd är ett träd där<br />

• varje nod har högst m underträd<br />

• nycklarna i en nod är en sekvens av upp till m-1 värden i<br />

stigande ordning som fungerar som delningspunkter vid<br />

sökning<br />

• till en nod med k+1 underträd t 0 ,t 1 ,...,t k hör en sekvens av<br />

k nycklar key 1 < key 2 < ... ,< key k . Sorteringsvillkoret för<br />

trädet är:<br />

alla nycklar i t 0 är mindre än key 1<br />

alla nycklar i t j , för 1


Generaliserade sökträd<br />

Generaliserade sökträd, <strong>ex</strong><br />

key1 key2 ...keyk<br />

l,p<br />

e,h<br />

m,n,o<br />

s<br />

a,b,c f,g i,j,k q,r t,u,v<br />

key1<br />

keyk<br />

t 0 t 1 t k<br />

AD: Träd 93<br />

AD: Träd 94<br />

B-träd<br />

B-träd är en typ av balanserat m-vägs sökträd. Det finns flera<br />

varianter. En av dem är följande:<br />

• roten är antingen ett löv eller så har den mellan 2 och m barn<br />

(dvs roten innehåller mellan 1 och m-1 nycklar)<br />

• för varje nod som inte är ett löv (utom möjligen roten) gäller<br />

m/2


1, 2, 3<br />

2-3-träd, <strong>ex</strong><br />

Nyckeln 3 sätts först också in i rotnoden, som<br />

då kommer att innehålla en nyckel för mycket.<br />

Noden splittras då i två noder. Mittnyckeln sätts<br />

normalt in i föräldernoden.<br />

Föräldernod finns ej i detta fall varför den bildas.<br />

Resten av nycklarna delas upp på två noder som får<br />

bli vänster resp höger barn till den nya noden:<br />

Split<br />

2<br />

1 3<br />

AD: Träd 97<br />

4 sätts in i rätt löv: 5 sätts in:<br />

2<br />

1 3, 4<br />

Och 7:<br />

2, 4<br />

1 3 5, 6, 7<br />

Split<br />

Problemnod<br />

2-3-träd, <strong>ex</strong><br />

2<br />

1 3, 4, 5<br />

Problemnod, splittra.<br />

Problemnod<br />

2, 4, 6<br />

1 3 5 7<br />

2, 4<br />

1 3 5<br />

Split<br />

Efter insättning av 6:<br />

2, 4<br />

1 3 5, 6<br />

AD: Träd 98<br />

2<br />

4<br />

6<br />

1 3 5 7<br />

B-träd, analys<br />

För ett B-träd av ordning m med höjden h och n nycklar insatta<br />

gäller att höjden h = O(log n).<br />

• För att välja rätt underträd för fortsatt sökning krävs att man ”stänger<br />

in” sökt nyckel mellan två nycklar i noden. Nycklarna i en nod<br />

förutsätts lagrade i växande ordning i en vektor. Man kan alltså<br />

använda binärsökning. Kostnaden för sökning i en nod är därför O(log m).<br />

• Nycklar måste också skiftas i den vektor där de lagras i samband med<br />

splittringar. Kostnaden för detta är O(m).<br />

Eftersom m är en konstant blir det O(1) arbete i varje nod vid sökning<br />

och insättning. Antalet noder som berörs är uppåt begränsat av höjden.<br />

==> Värstafallskostnad för sökning och insättning O(log n)<br />

AD: Träd 99<br />

Representation av trädstruktur med<br />

vektor<br />

Träd representeras vanligen av länkade strukturer. Även<br />

vektorer kan användas. Idén bakom vektorrepresentationen<br />

kan sedan användas för att lagra trädstrukturer på fil, vilket<br />

kan vara användbart när man inte kan ha hela trädet i<br />

primärminnet, men vill kunna söka snabbt.<br />

AD: Träd 100


Representation av trädstruktur med<br />

vektor, <strong>ex</strong><br />

Lagring av trädstruktur på fil<br />

3<br />

4<br />

2 8<br />

5 9<br />

Kan lagras i en vektor<br />

(Data-delen ej ifylld,<br />

–1 motsvarar null):<br />

left och right är nu heltal som<br />

”pekar” ut den plats i vektorn där<br />

vänster resp. höger barn finns. Man<br />

behöver bara känna till platsen där<br />

roten lagrats för att t <strong>ex</strong> kunna söka i<br />

trädet.<br />

Roten= 5<br />

0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

10<br />

Key Data Left Right<br />

5<br />

2<br />

8<br />

4<br />

3<br />

9<br />

-1 -1<br />

-1 8<br />

1 10<br />

3 4<br />

-1 -1<br />

-1 -1<br />

En fil är logiskt indelad i ett antal numrerade block. Vanlig<br />

blockstorlek är 512 bytes eller större. Ett block utgör den<br />

minsta enhet som kan läsas eller skrivas på fil.<br />

Vid lagring av ett binärt sökträd på fil så placeras i ett block<br />

nyckel, data samt nummer på de block på filen som<br />

innehåller vänster respektive höger underträd (analogt med<br />

hur vektorn används i <strong>ex</strong>emplet på förra bilden). Det enda<br />

man behöver känna till för att börja söka är numret på det<br />

block där roten finns.<br />

AD: Träd 101<br />

AD: Träd 102<br />

Lagring av trädstruktur på fil<br />

Tekniken kräver direktfiler, dvs att det finns operationer för<br />

att läsa och skriva på ett visst block på filen.<br />

Varje gång man går vidare till vänster eller höger underträd<br />

måste man läsa in ett nytt block från filen. Detta är en mycket<br />

mera kostsam operation än jämförelser mellan nycklar.<br />

Lagring av trädstruktur på fil<br />

B-träd kan på analogt sätt lagras på fil. Man kan då välja m<br />

så stort att det maximala antalet nycklar i en nod (m-1),<br />

referenser till deras data, samt blocknummer för det maximalt<br />

antal möjliga underträden får plats och fyller upp ett block.<br />

Typiskt m-värde kan vara 128 eller 256. För så stora m-<br />

värden kan väldigt många nycklar lagras i mycket låga träd t<br />

<strong>ex</strong> höjden 3 eller 4. Det blir mycket få läsningar av block för<br />

att hitta det man söker.<br />

Utnyttjas t <strong>ex</strong> i databaser.<br />

AD: Träd 103<br />

AD: Träd 104


ADT l<strong>ex</strong>ikon och träd<br />

Genom att använda träd (balanserade binära sökträd eller B-<br />

träd) vid implementation av ADTn l<strong>ex</strong>ikon (sökning, insättning,<br />

borttagning) får vi i värsta fall tidskompl<strong>ex</strong>iteten O(log n) för<br />

operationerna när det finns n element i samlingen.<br />

Man kan visa att detta är den undre gränsen för vad man kan<br />

uppnå om de enda operationer man får utföra på nycklarna är<br />

jämförelser.<br />

Det finns andra sätt att utnyttja nycklarna som gör att vi kan<br />

förbättra tidskompl<strong>ex</strong>iteten ytterligare. Vi skall se på en sådan<br />

möjlighet: Hashtabeller<br />

AD: Träd 105<br />

Hashtabeller<br />

Om enbart operationer för sökning, insättning och<br />

borttagning behövs kan vi använda datastruktur som ger<br />

bättre medelfallstider än binära sökträd.<br />

Idé: Element med nycklar 0, 1, 2, 3, ..., n–1 kan placeras i en<br />

vektor, det i:e elementet på plats i.<br />

Tid för insättning, sökning och borttagning blir O(1)<br />

0<br />

0<br />

1 2 3 4 5<br />

--------<br />

--------<br />

n-1<br />

1 2 3 4 5 n-1<br />

Dock: Alla typer av nycklar kan inte användas som ind<strong>ex</strong> i en<br />

vektor.<br />

AD: Träd 106<br />

Hashtabeller<br />

Hashtabeller i Java<br />

Idé: Avbilda nycklar på heltal.<br />

Nyckel<br />

Hash-funktion h<br />

Tal i intervallet 0..tableSize-1<br />

h avbildar stor mängd nycklar på en liten mängd tal.<br />

Kollisioner oundvikliga.<br />

Bra hashfunktion: Litet förväntat antal kollisioner, sprider<br />

elementen jämt över tabellen.<br />

Bör påverkas av alla delar av nyckeln.<br />

AD: Träd 107<br />

I klassen Object finns följande metod:<br />

/** returns a hash code value for the object */<br />

public int hashCode();<br />

Metoden returnerar ett heltal. För att hamna i rätt intervall kan<br />

sedan % användas. x.hashCode()%tableSize ger ett heltal i<br />

intervallet 0 .. tableSize-1.<br />

Brukligt att omdefiniera hashCode för de objekt man tänker<br />

sätta in i hashtabeller. Man måste se till att två objekt för vilka<br />

equals returnerar true också har samma hashkod för att<br />

sökning i en hashtabell skall fungera.<br />

AD: Träd 108


Sluten hashtabell<br />

Sluten hashtabell, <strong>ex</strong><br />

Det finns olika sätt att implementera den grundläggande idén<br />

för hashtabeller. De skiljer sig på dels vilken datastruktur som<br />

används och också på hur de hanterar kollisioner, d.v.s. hur<br />

man hanterar insättning av objekt när det redan finns ett eller<br />

flera insatta objekt med samma hashkod.<br />

I s.k. sluten hashtabell används en vektor för att lagra<br />

elementen. Det finns sedan olika sätt att hantera kollisioner.<br />

Vid linjär teknik sätter man in ett element som kolliderar med<br />

ett annat på första lediga plats efter den där det skulle ha<br />

hamnat om ingen kollision inräffat. Tabellen betraktas därvid<br />

som cirkulär, d.v.s. plats 0 anses komma efter tableSize-1.<br />

AD: Träd 109<br />

Ex: Sätt in talen 1, 8, 27, 64, 125 i en tabell med 7 platser.<br />

Använd hashfunktionen h(x) = x % 7 och linjär teknik vid<br />

kollisioner.<br />

0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

1<br />

0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

1<br />

8<br />

0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

1<br />

8<br />

27<br />

0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

1<br />

8<br />

64<br />

27<br />

125<br />

1<br />

8<br />

64<br />

Sökning efter visst element börjar på den plats elementets<br />

hashkod anger och fortsätter eventuellt framåt. Om det<br />

inte påträffas före en ledig plats finns det inte i tabellen.<br />

AD: Träd 110<br />

0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

27<br />

Problem med linjär teknik<br />

Linjär teknik ger upphov till primär klustring itabellen.Om<br />

flera objekt har samma hashkod hVal kommer de alla att<br />

ligga i ett kluster kring platsen hVal i tabellen. Även objekt<br />

vars hashvärden är nära hVal kommer att drabbas av<br />

kollisioner och bygga ut klustret.<br />

Ex: hashfunktion x %11. Sätt in talen 3, 14, 25, 36, 5, 16<br />

3 14 25 36 5 16<br />

0 1 2 3 4 5 6 7 8 9 10<br />

Stora kluster gör sökning långsam.<br />

Borttagning i sluten hashtabell vid linjär<br />

teknik<br />

Om vi vid borttagning bara gör platsen tom, leder det till fel<br />

vid sökning. Ex: Tag bort 25 ur tabellen på föregående bild:<br />

3 14 36 5 16<br />

0 1 2 3 4 5 6 7 8 9 10<br />

Om vi nu söker efter 5 vars hashkod är 5 börjar vi pröva<br />

plats 5. Eftersom denna plats är tom sluter vi oss felaktigt<br />

till att det sökta ej finns i tabellen.<br />

AD: Träd 111<br />

AD: Träd 112


Borttagning i sluten hashtabell vid linjär<br />

teknik<br />

Om vi i stället markerar platsen ”icke-aktiv” vid borttagning (<br />

i fig. nedan markerat med ett d):<br />

3 14 d 36 5 16<br />

0 1 2 3 4 5 6 7 8 9 10<br />

så kan vi utföra sökningen med början på den plats<br />

hashkoden anger och framåt över alla upptagna och ickeaktiva<br />

platser.<br />

Först när vi stöter på en riktigt tom plats är det misslyckad<br />

sökning.<br />

AD: Träd 113<br />

Tidskompl<strong>ex</strong>itet, linjär teknik<br />

Värstafallet för samtliga operationer är O(n), där n är antalet<br />

element som finns insatta i tabellen.<br />

Inträffar om alla element hamnar i en följd och vi t <strong>ex</strong> vid<br />

sökning måste pröva alla platserna i denna följd.<br />

Är dock ytterst osannolikt.<br />

Under förutsättning att tabellen inte fylls till mer än en viss del<br />

får man emellertid O(1)-kompl<strong>ex</strong>itet i medeltal för operationerna.<br />

(Se följande bilder).<br />

AD: Träd 114<br />

Tidskompl<strong>ex</strong>itet, linjär teknik<br />

Man definierar en (sluten) hashtabells fyllnadsgrad (load<br />

factor) som kvoten mellan antal insatta element och antal<br />

platser i tabellen.<br />

En tom tabell har fyllnadsgrad 0 och en full tabell har<br />

fyllnadsgrad 1. Man brukar beteckna fyllnadsgraden med λ .<br />

Man kan visa att: (se nästa bild)<br />

AD: Träd 115<br />

Tidskompl<strong>ex</strong>itet, linjär teknik<br />

• Medelantalet platser som måste prövas vid en insättning<br />

och misslyckad sökning när man använder linjär teknik är<br />

uppåt begränsat av<br />

( 1 + 1/(1 – λ) 2 )/2<br />

Ex. Om λ = 0.5 blir det i medeltal 2.5<br />

• Medelantalet platser som prövas vid en lyckad sökning är<br />

uppåt begränsat av<br />

(1 + 1/(1 - λ))/2<br />

Ex. Om λ = 0.5 blir det i medeltal 1.5<br />

Men: vid hög fyllnadsgrad blir operationerna långsamma när<br />

den linjära tekniken används.<br />

AD: Träd 116


Sluten hashing med kvadratisk teknik vid<br />

kollisioner<br />

Kvadratisk teknik:<br />

● Alternativ, bättre teknik för hantering av kollisioner<br />

● Först prövas nästa plats, sedan platsen 4 steg fram,<br />

sedan 9 steg fram, alltså H, H+1, H+4, ... H+i 2 ,...,där<br />

H är elementets hashkod. Tabellen används fortfarande<br />

cirkulärt.<br />

● Undviker primär klustring av element. Kan modifieras<br />

till andra sekvenser av steg.<br />

Sluten hashing med kvadratisk teknik vid<br />

kollisioner<br />

Ex: Sätt in talen 89, 18, 49, 58, 9 i en tabell med 10 platser.<br />

Hashfunktion: x%10<br />

89%10 = 9, 18%10=8, 49%10=9, 58%10=8, 9%10=9<br />

49 58 9<br />

18 89<br />

0 1 2 3 4 5 6 7 8 9<br />

AD: Träd 117<br />

AD: Träd 118<br />

Sluten hashing med kvadratisk teknik vid<br />

kollisioner<br />

Sluten hashing med kvadratisk teknik vid<br />

kollisioner<br />

Problem: Inte alltid säkert att man hittar ledig plats även om<br />

det finns. Om t <strong>ex</strong> tabellens storlek är 16 och man använder<br />

hashfunktionen x%16 och sätter in talen 0, 16, 32 och 64 så<br />

kan man inte därefter hitta någon ledig plats för tal som<br />

hashas till plats 0. De enda platser som kommer att prövas i<br />

serien H+i 2 när H=0 blir de upptagna platserna 0, 1, 4 och 9.<br />

AD: Träd 119<br />

Man kan visa att<br />

• Om kvadratisk teknik används och tabellens storlek är ett<br />

primtal så kan ett nytt element alltid sättas in om tabellens<br />

fyllnadsgrad är mindre än 0.5<br />

• Det har ännu inte gjorts någon fullständig analys av<br />

kompl<strong>ex</strong>iteten hos operationerna på tabellen när kvadratisk<br />

teknik används. I praktiken visar sig kvadratisk teknik ge<br />

upphov till mindre klustring än den linjära tekniken och<br />

därmed snabbare operationer. (Dock fortfarande O(n) i<br />

värsta fall).<br />

AD: Träd 120


Öppen hashtabell (separate chaining)<br />

Öppen hashtabell (separate chaining)<br />

Elementen i tabellen är listor. I lista nummer i ligger alla<br />

element vars nyckel hashfunktionen avbildar på i.<br />

Ex: Sätt in 7, 9, 14, 12, 21, 19 i en öppen tabell med 7<br />

ingångar. Använd hashfunktionen f(x) = x % 7<br />

0<br />

1<br />

2<br />

tableSize-2<br />

tableSize-1<br />

0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

9<br />

12<br />

14<br />

19<br />

21<br />

AD: Träd 121<br />

AD: Träd 122<br />

Tidskompl<strong>ex</strong>itet, öppen hashing<br />

Tidskompl<strong>ex</strong>itet, öppen hashing<br />

Värstafall: O(n) för samtliga operationer. Inträffar när alla de<br />

n insatta elementen hamnat i samma lista.<br />

Medelfall: Antag att vi har n insatta element fördelade på de<br />

tableSize olika listorna.<br />

Insättning av nytt element x: Antag att det är lika sannolikt att<br />

x hashas till var och en av listorna. Eftersom den lista (k)<br />

där x skall placeras måste genomletas efter dubblett först så<br />

kostar själva insättningen där = antalet element i listan k. I<br />

medelfall blir det: (forts)<br />

AD: Träd 123<br />

tableSize-1<br />

A(n) = Σ(längden av lista i)/tableSize = n/tableSize<br />

i=0<br />

Spec: Om n tableSize är A(n) = 1<br />

Misslyckad sökning = insättning. Lyckad sökning kan<br />

visas i medeltal kosta:<br />

(n-1)/(2*tableSize) +1.<br />

Tumregel för öppen tabell: Inte fler än 2*tableSize<br />

element bör sättas in.<br />

AD: Träd 124


Sammanfattning om olika<br />

implementationsalternativ för l<strong>ex</strong>ikon<br />

Lista: Bara om antalet element förväntas bli litet. Annars<br />

ineffektivt.<br />

BST: Bra medelfall O(log n) för operationer, men dåligt<br />

värstafall, O(n). Måste balanseras för att uppnå O(log n)<br />

även i värsta fall.<br />

B-träd: När man måste lagra träden på sekundärminne. I<br />

primärminne används de nästan bara i specialfallet 2-3-träd,<br />

som alternativ till balanserade BST.<br />

Hash-tabell: Väldigt bra medelfall O(1) men dåligt värstafall, O(n). Kan<br />

inte väljas om sådant värstafall inte kan tillåtas. Inte heller<br />

om man vill ha ytterligare operationer såsom ”sök minsta”<br />

eller andra som bygger på elementens inbördes<br />

storleksordning.<br />

AD: Träd 125<br />

Hashtabeller i Java<br />

I klassen java.util finns klassen HashMap som implementerar<br />

interfacet Map. Till skillnad från klassen TreeMap så måste inte<br />

de insatta nycklarna ha någon ordningsrelation definierad,<br />

däremot behöver man oftast omdefiniera metoderna<br />

public int hashCode() och<br />

public boolean equals(Object x)<br />

så att man får identisk hashkod för objekt som är lika enligt<br />

metoden equals.<br />

Anm: För flera av Javas egna klasser är detta redan gjort. T <strong>ex</strong><br />

klassen String.<br />

AD: Träd 126<br />

Användning av HashMap<br />

Användning av HashMap<br />

Antag vi vill vill sätta in Person-objekt i en hashtabell, med<br />

nyckel = personens namn:<br />

class Person {<br />

String name; // namn<br />

long pNbr; // personnummer<br />

... // övriga attribut<br />

public Person(String n, int pnbr) {...}<br />

public boolean equals(Object rhs) {<br />

return name.equals(((Person) rhs).name);<br />

}<br />

// andra metoder i klassen Person<br />

HashMap reg =<br />

new HashMap();<br />

Person p = new Person(”Kalle”, 1111111111);<br />

reg.put(p.name, p);<br />

...<br />

Person q = reg.get(”Kalle”);<br />

if (q != null) {<br />

…<br />

}<br />

Observera att vi här inte behöver omdefiniera hashCode<br />

eftersom nycklarna är av typen String, och man i denna klass<br />

redan gjort detta.<br />

} AD: Träd 127<br />

AD: Träd 128


Klassen HashSet i Java<br />

Användning av HashSet<br />

Det finns ytterligare en klass i java.util som utnyttjar<br />

hashtabeller för sin implementation, klassen HashSet. Den<br />

implementerar interfacet Set och de viktigaste operationerna<br />

är:<br />

boolean add(E x);<br />

boolean contains(Object x);<br />

boolean remove(Object x);<br />

Iterator iterator();<br />

Det finns alltså ingen riktig sökoperation.<br />

AD: Träd 129<br />

Om vi vill sätta in Person-objekt i en samling av typen<br />

HashSet och gör så här:<br />

HashSet reg = new HashSet();<br />

Person p = new Person(”Kalle”, 1111111111);<br />

reg.add(p);<br />

...<br />

if (reg.contains(new Person(”Kalle”,0)) {<br />

System.out.println(”found”);<br />

} else {<br />

System.out.println(”not found”);<br />

}<br />

så blir utskriften med största sannolikhet ”not found”.<br />

AD: Träd 130<br />

Användning av HashSet<br />

Anledningen är att när Kalle sätts in beräknas hashkoden för<br />

objektet som p refererar till och placeringen i tabellen beror<br />

på denna. När vi sedan söker efter Kalle baseras sökningen<br />

på hashkoden av det objekt som är parameter till containsmetoden<br />

och detta är ett annat objekt (med samma namn).<br />

Sökningen utgår från den plats denna senare hashkod anger<br />

och med största sannolikhet är det i en helt annan del av<br />

tabellen än den där Kalle sattes in.<br />

AD: Träd 131<br />

Användning av HashSet<br />

Vi kan se till att alla Person-objekt som har samma namn<br />

också får samma hashkod genom att omdefiniera metoden<br />

hashCode i klassen Person:<br />

class Person {<br />

String name; // namn<br />

long pNbr; // personnummer<br />

... // övriga attribut<br />

public Person(String n, int pnbr) {…}<br />

public boolean equals(Object rhs) {som förut}<br />

public int hashCode() {<br />

return name.hashCode();<br />

}<br />

// övriga metoder i klassen Person<br />

}<br />

AD: Träd 132

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!