Datastrukturer
föreläsning 11
Hur gamla är
datastrukturerna?
Hashtabeller 1953
Binära sökträd 1950-talet
AVL-träd 1962
B-träd, rödsvarta träd 1972
Skipplistor 1990
Animeringar
Skipplistor
-∞ +∞
S0 S1 S2 S3
-∞ 10 15 23 36 +∞
-∞ 15 +∞
-∞ 15 23 +∞
Skip lists (Pugh 1990)
“Skip lists are a probabilistic data
structure that seem likely to supplant balanced trees as the implementation method of choice for many applications.
Skip list algorithms have the same
asymptotic expected time bounds as
balanced trees and are simpler, faster
and use less space.”
Vad är en skipplista?
en lista av sorterade listor S0, S1 , … , Sh.
Varje Si innehåller +∞ och -∞ (särskilda största och minsta element)
Varje lista är en dellista av den efterföljande, S0 ⊆S1 ⊆ … ⊆ Sh
Listan Sh innehåller bara +∞ och -∞. Sh-1 innehåller minst ett riktigt element!
56 64 78 +∞
31 34 44
-∞ 12 23 26 -∞ +∞
31 +∞
-∞
64 +∞
31 34
-∞ 23
S0 S1 S2 S3
Implementering av skipplistor (enligt G&T)
Använd länkad struktur!
Varje nod innehåller:
element
länkar till noderna efter och före
länkar till noderna under och över (som innehåller samma element!)
Obs. Figuren på föregående bild visar bara länkar till noderna före och efter. Det ska även finnas länkar vertikalt.
Sökning i skipplista
Sök efter nyckel k!
Börja med -∞ i översta listan
Antag att vi har kommit till en nod med nyckeln x k = x: vi har hittat nyckeln!
k > x: gå åt höger!
k < x: gå tillbaka och nedåt! Vi har gått för långt!
Om vi försöker gå nedåt från understa listan finns inte nyckeln.
Exempel: leta efter 78! Leta efter 77!
-∞ +∞
S S1 S2
S
3 -∞ 31 +∞
64 +∞
31 34
-∞ 23
56 64 78 +∞
31 34 44
-∞ 12 23 26
Att skapa en skipplista
Vi skapar en skiplista som representerar den sorterade listan 12, 23, 26, 31, 34, 44, 56, 64, 78. Denna lista inkl -
∞, +∞
blir den understa listan S0.Varje element i Si har 50% chans att bli medlem i Si+1.
- vi gör slumpmässiga val.
-∞ +∞
S S1 S2
S
3 -∞ 31 +∞
64 +∞
31 34
-∞ 23
56 64 78 +∞
31 34 44
-∞ 12 23 26
Algoritmer med slumpval
Innehåller instruktioner b ← random()
Javainstruktion:
Math.random() ger slumpgenererade
flyttal mellan 0 och 1
Exekveringstiden beror på slumpen och är ofta dålig i värsta fall, men sannolikheten för värsta fallet brukar vara låg.
När man skapar skipplistor och gör
insättning i skipplistor använder man
slumpmässiga val.
Vi gör först en sökning (som innan) för att hitta rätt plats för elementet i den understa listan.
Vi gör slumpmässiga val (kastar mynt) för att bestämma hur många extra kopior av
elementet vi ska göra. Vi kan tänka oss att vi kastar ett mynt för att avgöra om elementet ska vara med i nästa lista. Om så är fallet sätter vi in det ovanför, osv.
Obs att vi eventuellt kommer att behöva öka antalet nivåer i skipplistan.
Insättning i skipplista
Exempel: sätt in nyckeln 15, med 2 extra kopior.
Insättning i skipplista
-∞ 10 36 +∞
-∞ +∞
23
23 +∞
-∞
S0 S1 S2
-∞ +∞
S1 S2 S3
-∞ 10 15 23 36 +∞
-∞ 15 +∞
-∞ 15 23 +∞
S0
Sätt in nyckeln 27 med 1 extra kopia.
Insättning, ett exempel till …
-∞ +∞
S1 S2 S3
-∞ 10 15 23 27 +∞
-∞ 15 +∞
-∞ 15 23 +∞
S0
-∞ +∞
S1 S2 S3
-∞ 10 15 23 36 +∞
-∞ 15 +∞
-∞ 15 23 +∞
S0
36 27
Borttagning ur skipplista
Ta bort elementet k:
Vi söker efter k som i sökalgoritmen och hittar noderna p0, p1 , …, pi som ligger just före noden med k, i listorna S0, S1, … , Si
Vi tar bort noderna k från S0, S1, … , Si
Vi tar bort alla utom en av de listor som bara innehåller -∞ och +∞
Exempel: ta bort 34!
-∞ 12 45 +∞
-∞ +∞
23
-∞ 23 +∞
S S1 S2
-∞ +∞
S S1 S2 S3
-∞ 12 23 34 45 +∞
-∞ 34 +∞
-∞ 23 34 +∞
p0 p1 p2
Alternativ implementering av
skipplista
Sammanfattning
En skipplista byggs upp med slumpmässiga val.
En skipplista som
representerar en mängd med n element
använder O(n) utrymme i medeltal (varför?)
Medeltiden för sökning, insättning och
borttagning är O(log n)
Sannolikheten för god tidskomplexitet är hög (avancerad
sannolikhetsanalys
behövs dock för att visa detta, se kurs i
matematisk statistik)
En effektiv datastruktur som är besläktad med sökträd!
Några effektiva
implementeringar av lexika
Sökning Insättning Borttagning Kommentar
AVL-träd O(log n) O(log n) O(log n) -omstruktureringskostnader
Skipplistor O(log n)
i medeltal O(log n) i medeltal
O(log n) i medeltal
-slumpmässig algoritm
Röd-svarta
träd O(log n) O(log n) O(log n)
-Ommålning men färre omstruktureringar!
-TreeMap i Java
Några effektiva
implementeringar av lexika
Sökning Insättning Borttagning Kommentar
Splayträd O(log n)
amorterad
O(log n) amorterad
O(log n) amorterad
-effektivt för vanligaste nycklar
B-träd O(logB n)
I/O O(logB n) I/O
O(logB n)
I/O -för stora lexika/databaser
Implementeringar av abstrakta datatyper
Specifikationer och korrekthet
ADT för avbildningar
Operationer:
get(k)
remove(k)
put(k,v)
size()
containsKey(k)
isEmpty()
keys()
values()
Några axiom
1. D.isEmpty() = (D.size() == 0) 2. D.put(k,e).get(k) = e
3. D.put(k,e).remove(k) = e
4. D.remove(k).put(k,e) = D.put(k,e) om D.containsKey(k) = false
5. D.put(k,e).size() = D.size() om D.containsKey(k) = true 6. D.put(k,e).size() = D.size() + 1
om D.containsKey(k) = false
7. D.remove(k) = D if D.containsKey(k) = false
8. D.put(k,e).put(k’,e’) = D.put(k’,e’).put(k,e) om k != k’
Enhetstestning
Skriv ner testfall för den abstrakta datatypen innan den implementeras (”black-box testing”)
Jfr QuickCheck för Haskell!
Hur övertygar man sig om att en implementering är korrekt?
Testa varje enhet!
Testa hela programmet!
Granska koden och försök att bevisa att den är korrekt!
Se kursen om programverifiering!
Sortering
1 2 7 7 9 11 13
Sortering med sökträd
Man kan sortera med binära sökträd.
Man gör insättningar följd av en inorder traversal. Denna tree-sort är O(n
2) i
värsta fall.
Om man använder balanserade sökträd (t ex AVL-träd eller rödsvarta träd) får man en tree-sort som är O(n log n) i värsta fall.
Man kan även sortera med skipplistor
Sortering med prioritetskö
1.
Sätt in elementen från indatalistan i enprioritetskö genom att använda insertItem upprepade gånger
3.
Plocka ut elementen ur prioritetskön genom att använda removeMin upprepade gånger och lägg dem i utdatalistanExekveringstiden beror på hur prioritetskön är implementerad
-
osorterad lista O(n2) – selection sort-
sorterad lista O(n2) – insertion sort-
heap O(n log n) - heapsortSortering med prioritetskö
Algorithm PQ-Sort(S, C)
Input stack S, comparator C for the elements of S
Output stack S sorted in decreasing order according to C P ← priority queue with comparator C
while ¬S.isEmpty ()
e ← S.pop() {O(1)}
P.insertItem(e) {beror på vilken slags prioritetskö}
while ¬P.isEmpty()
e ← P.removeMin() {beror på vilken slags prioritetskö}
S.push(e) {O(1)}
Selection sort
Prioritetskö med osorterad lista
Komplexitetsanalys:
1. Tiden det tar att göra n insertItem är O(n) 2. Tiden det tar att plocka ur elementen med n
removeMin operationer är proportionell mot
n + … + 2 + 1 = n(n+1)/2
Selection sort är O(n
2)
Insertion sort
Prioritetskö med sorterad lista
Exekveringstid
1. Tiden det tar att sätta in elementen med n insertItem operationer är i värsta fallet proportionell mot
1 + 2 + …+ n = n(n+1)/2
2. Tiden det tar att plocka ur elementen med n removeMin operationer är O(n)
Insertion sort är O(n
2) i värsta fallet
In-place sortering
Vi kan implementera selection sort,
insertion sort och heap-sort “in-place”
In-place betyder att vi bara använder O(1) extra utrymme (utöver de O(n) som
behövs för att lagra indatalistan) för att sortera n element
Använd ett fält och swapElements(i,j) som
byter plats på elementen i cell i och j
Insertion sort in-place
5 4 2 3 1
5 4 2 3 1
4 5 2 3 1
2 4 5 3 1
2 3 4 5 1
1 2 3 4 5
Sorterad e element
är blå
Osorterade element är
bruna
Heap-sort
Använd en
prioritetskö som är implementerad med en heap
insertItem och removeMin är O(log n)
Heap-sort är alltså O(n log n)
och alltså mycket
snabbare (i värsta
fallet) än insertion-
sort och selection-
sort som är O(n
2)
Heap-sort in-place
4 5 1 3 7
4 5 1 3 7
4 5 1 3 7
7 5 1 3 4
4 5 1 3 7
3 4 1 5 7
4 3 1 5 7
1 3 4 5 7
3 1 4 5 7
1 3 4 5 7
1 3 4 5 7
Bygg en blå
max- heap
3 steg:
bubbla 1,3,7 till rätt
plats
Färdig max- heap
De utplockade elementen bygger upp ett sorterat gult fält bakifrån