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
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