Programkonstruktion och
Datastrukturer
Repetitionskurs, sommaren 2011
Datastrukturer
(Listor, Träd, Sökträd och AVL-träd)
Elias Castegren
[email protected]
Datastrukturer
●
Vad är en datastruktur?
●
Ett sätt att lagra och organisera data på ett effektivt
sätt i en dator.
–
●
●
Wikipedia
En datastruktur kan lagra ett eller fl era värden i
samma struktur.
Olika datastrukturer har ofta olika för- och
nackdelar.
Array
●
En array (eller en vektor*) är en datastruktur
med n celler, som ofta indexeras från 0 till n-1.
...
0
●
●
1
2
3
n-3
n-2
n-1
Att lagra eller hämta ett värde från en godtycklig
cell går på konstant tid.
Vill man utvidga arrayen måste man skapa en
ny, större array och kopiera de gamla värdena.
●
”Bakom kulisserna” är en array en obruten sekvens
av ettor och nollor i minnet.
*I ML är en array en vektor där alla celler är referenser.
Array
●
●
●
●
En stor nackdel med arrayer är att de använder
lika mycket minne oavsett hur många celler
som innehåller lagrade värden.
Man är också låst till den övre gräns man sätter
när man anger antalet celler i arrayen.
Att hitta en bra balans mellan
minnesanvändning och antalet tillgängliga celler
kan vara svårt!
Ett alternativ är att använda en rekursiv
datastruktur.
Rekursiva datastrukturer
●
●
En rekursiv datastruktur är en datastruktur som
lagrar ett eller fl era värden som också är
samma sorts datastruktur.
Det enklaste exemplet är en lista.
Listor
●
En lista (som inte är tom) innehåller ett värde
och en till lista som är resten av värdena:
datatype 'a list = nil | Cell of 'a * 'a list;
v1
v2
v3
v4
Cell(v1, Cell(v2, Cell(v3, Cell(v4, nil))))
●
●
Använder bara så mycket minne som behövs
för de värden som fi nns lagrade för tillfället.
För att utvidga listan tillfogar man bara en till
list-cell.
(Självklart använder man MLs inbyggda syntax för listor)
Listor
●
Även om vi har vunnit möjligheten att utvidga
och krympa datastrukturen efter behov (listor är
en dynamisk datastruktur) så har vi förlorat
möjligheten att lagra och hämta värden på
konstant tid.
●
●
Vi ”ser” bara den första list-cellen. För att hitta en
specifi k cell måste vi gå genom alla föregående
celler.
Tidskomplexiteten för åtkomst i datastrukturen har
gått från O(1) för arrayer till O(n) för listor.
Rekursiva datastrukturer
●
●
●
En rekursiv datastruktur är en datastruktur som
lagrar ett eller fl era värden som också är
samma sorts datastruktur.
Det enklaste exemplet är en lista.
Strukturer som innehåller fl er än en
strukturrekursion kallas för trädstrukturer.
Träd
●
●
●
Ett träd består av noder som kan ha ett fast
eller godtyckligt antal undernoder som också är
träd.
En nod som inte har några undernoder kallas
för ett löv.
Binärt träd:
datatype 'a tree =
nil | Node of 'a * 'a tree * 'a tree
●
Triärt träd:
datatype 'a tree =
nil | Node of 'a * 'a tree * 'a tree * 'a tree
●
Generellt träd:
datatype 'a tree = Node of 'a * 'a tree list
Träd
●
Binärt träd:
v1
v2
v4
●
v5
Generellt träd:
Node(v1,
Node(v2,
Node(v4, nil, nil)
v3
Node(v5, nil, nil)),
Node(v3,
Node(v6, nil, nil)
v6
nil))
Node(v1,
[Node(v2,
[Node(v5, []),
Node(v6, [])]),
v2
v3
v4
Node(v3, []),
Node(v4,
[Node(v7,
v5
v6
v7
[Node(v8, []),
Node(v9, []),
v8
v9
v10
v11
Node(v10, [],
Node(v11, [])]]])
v1
Träd
●
Träd är också dynamiska datastrukturer men
tidskomplexiteten för åtkomst är fortfarande
linjär.
●
●
Man måste söka igenom alla vägar i trädet för att
vara säker på att hitta en viss nod.
Binära sökträd råder bot på detta!
Binära sökträd
●
Ett binärt sökträd är ett binärt träd där varje nod
dessutom innehåller ett unikt värde kallat
nodens nyckel:
datatype 'a searchTree =
nil | int * 'a * 'a searchTree * 'a searchTree
Nyckel Data
●
Vänster delträd
Höger delträd
Noderna i ett sökträd är ordnade så att alla
noder i vänster delträd har en nyckel som är
mindre än rotnodens, medan alla noder i höger
delträd har en nyckel som är större än
rotnodens.
Binära sökträd
●
Om vi letar efter noden som har nyckeln k och
noden vi tittar på just nu har nyckeln k' gör vi
följande val:
●
●
●
●
Om k=k' så har vi hittat rätt nod och kan sluta leta.
Om k<k' så måste noden vi söker ligga i vänster
delträd, alla nycklar i det högra delträdet har ju
nycklar som är strikt större än k'.
Om k>k' så måste noden vi söker ligga i höger
delträd, med samma resonemang som ovan.
I varje steg kan vi alltså utesluta ett delträd från
sökningen!
Binära sökträd
●
Leta efter noden med nyckeln 3
●
Om trädet är balanserat (delträdens höjder skiljer
inte allt för mycket):
–
–
–
–
3<5
3>2
3<4
3=3
5
2
1
7
4
5
8
3
●
I varje steg försvinner (ungefär) hälften av de
återstående noderna:
●
T(n) = T(n/2) + O(1)
Andra fallet av Master theorem => T(n) = O(lg(n))
Binära sökträd
●
Leta efter noden med nyckeln 3
●
Om trädet är obalanserat:
–
–
–
–
–
●
3<7
3<6
3<5
3<4
3=3
3
4
5
6
7
I varje steg försvinner bara en av noderna (den
vi undersöker för tillfället)
T(n) = T(n-1) + O(1)
T(n) = O(n)
Binära sökträd
●
●
●
I binära sökträd har vi en dynamisk datastruktur
med potentiellt logaritmisk tidskomplexitet för
åtkomst. Inte lika snabb som en array men
ändå en god konkurrent!
Bara effektiv om sökträdet är balanserat. I
värsta fall beter den sig precis som en lista.
Hur får man ett balanserat sökträd?
●
●
Om nycklarna anländer helt slumpmässigt blir trädet
(tillräckligt) balanserat för bra prestanda.
Man använder ett självbalanserande sökträd!
AVL-träd
●
●
●
Ett AVL-träd är ett binärt sökträd där varje nod
också innehåller nodens balansfaktor.
Balansfaktorn är den längsta vägen till ett löv i
det högra delträdet minus den längsta vägen till
ett löv i det vänstra delträdet.
Möjliga balansfaktorer i ett AVL-träd:
●
●
●
●
●
-2 ”Vänster-obalanserat”
-1 ”Vänstertungt”
0 ”Balanserat”
1 ”Högertungt”
2 ”Höger-obalanserat”
AVL-träd
●
Ett AVL-träd kan balansera sig självt med hjälp
av rotationer:
(A, B och C är delträd)
X
Y
A
●
●
A
C
B
Y
Högerrotation
Vänsterrotation
X
B
C
Vi säger att vi högerroterar trädet eller
högerroterar mot noden x.
En rotation kan utföras på konstant tid.
AVL-träd
●
●
●
Varje gång man lägger till en nod i ett AVL-träd
uppdaterar man berörda noders balansfaktorer.
Så länge en nod är balanserad eller tung åt
något håll (balansfaktorn är 0, -1 eller 1) så gör
man ingenting.
Om en nod blir är obalanserad (balansfaktor -2
eller 2) måste man rotera för att återställa
balansen.
AVL-träd
●
Fyra fall där balansering behövs:
●
Noden n är vänster-obalanserad
–
–
●
Vänstra delträdet är vänstertungt:
1. Högerrotera mot noden n
Vänstra delträdet är högertungt
1. Vänsterrotera vänstra delträdet
2. Högerrotera mot noden n
Noden n är höger-obalanserad
–
–
Högra delträdet är högertungt:
1. Vänsterrotera mot noden n
Högra delträdet är vänstertungt
1. Högerrotera högra delträdet
2. Vänsterrotera mot noden n
AVL-träd
●
Fyra fall där balansering behövs:
●
”Vänster vänster”:
-2
X
-1
Högerrotation
mot X
Y
0
Y
0
X
Z
0
0
Z
●
”Vänster höger”:
-2
-2
X
Vänsterrotation
mot Y
+1
Y
0
Z
●
-1
Z
0
X
Högerrotation
mot X
0
0
Y
Z
X
0
Y
Motsvarande fast spegelvänt gäller då rotnoden (X)
är höger-obalanserad
AVL-träd
●
●
Ett AVL-träd är alltså alltid balanserat, så när
som på att höjden av en nods två delträd kan
skilja med 1 (om noden är vänster- eller
högertung).
Åtkomst fungerar på samma sätt som i ett
binärt sökträd
T(n) = O(lg(n))
●
Insättning kräver som mest två traverseringar
till rätt plats i trädet (en för att sätta in noden
och en för att räkna ut nya balansfaktorer) plus
som mest två rotationer
T(n) = 2•O(lg(n)) + 2•O(1) = O(lg(n))
AVL-träd
●
Kom ihåg: Komplexitet säger inget om körtiden!
●
●
●
●
Även om AVL-träd har en bra komplexitet så är det
förstås mindre minnes- och beräkningskrävande att
använda ett vanligt binärt sökträd.
Rotation och balansering går snabbt men är inte
gratis. I en situation där det krävs många
insättningar blir det också mycket jobb att
balansera.
För små datamängder kan det vara bättre att
använda en enklare datastruktur.
En bra applet för att experimentera med AVLträd fi nns här:
http://www.qmatica.com/DataStructures/Trees/AVL/AVLTree.html
Övningar
●
Sätt in nycklarna 5, 3, 8, 2, 4, 1, 7, 9, 6 i ett tomt
AVL-träd. Rita hela trädet (med balansfaktorer)
efter varje insättning.
–
●
Övningen (med lösningsförslag) gavs till SI-möte 8
Vilka datastrukturer kan vara lämpliga om man
vill lagra följande information:
–
–
–
Medlemsregistret i en hemlig klubb, nycklar är
medlemmarnas personnummer
Medlemsregistret i en ännu hemligare klubb, nycklar är
datum och klockslag då man gick med i klubben
Registreringsskyltarna för bilarna som står parkerade i ett
parkeringshus, nycklar är parkeringsplatsernas nummer.