Datastructuren
Quicksort en andere
sorteermethoden
College 3
1
Vandaag
 Quicksort
 Randomized quicksort
 Meer over analyse van algoritmen
 Heaps en heapsort
2
Datastructuren
Quicksort
 Verdeel en heers paradigma
 Idee is:
 Kies een element uit de array, zeg x
 Splits de array in drie stukken:
• Alles in 1e stuk is ≤ x
• 2e stuk is het element x
• Alles in 3e stuk is ≥ x (of >)
 Sorteer recursief het eerste stuk
 Sorteer recursief het derde stuk
 Klaar!
3
Opmerking
 In onze beschrijving gaan we er van uit dat
alle elementen verschillend zijn
 Als er gelijke elementen zijn, werkt het ook,
maar moet je iets beter opletten in de
analyse (zelfde code kan gebruikt worden)
4
Datastructuren
Quicksort: Eén
PARTITION
5
Datastructuren
Splitsen
Partition(A,p,r)
 {Input is array A met
indexwaardes van p tot
en met r}
 {Output: waarde q met
p ≤ q ≤ r zodat A[p..r]
een permutatie is van
input, en als p ≤ i < q
dan geldt A[i] ≤ A[q] en
als q < i ≤ r dan geldt
A[i] > A[q]}
 …
6
 Methode partitioneert
array A[p…r]
 Returnwaarde is plek
waar “splitselement”
terechtgekomen is
 Splitselement heet
pivot en nemen we nu
als element dat op A[r]
staat
Partition
 Code in boek is subtiel
Allemaal ≤
p
Allemaal >
i i+1
r
j-1 j
Gebied waar we nog
aan werken
7
pivot
Pseudocode Partition
Partition(A,p,r)
 pivot = A[r];
 i = p – 1;
 for j = p to r – 1 do
 {*}
 if A[j] ≤ pivot
 then
• i ++;
• Verwissel A[i] en A[j]
 Verwissel A[i+1] en
A[r];
 return i+1;
8
 Invariant: bij * geldt
voor elke k, p ≤ k ≤ r:
1. Als p ≤ k ≤ i, dan A[k]
≤ pivot
2. Als i+1 ≤ k ≤ j – 1,
dan A[k] > pivot
3. Als k=r, dan
A[k]=pivot
Pseudocode Partition
Partition(A,p,r)
 pivot = A[r];
 i = p – 1;
 for j = p to r – 1 do
 {*}
 if A[j] ≤ pivot
 then
• i ++;
• Verwissel A[i] en A[j]
 Verwissel A[i+1] en
A[r];
 return i+1;
9
 Invariant: bij * geldt
voor elke k, p ≤ k ≤ r:
1. Als p ≤ k ≤ i, dan A[k]
≤ pivot
2. Als i+1 ≤ k ≤ j – 1,
dan A[k] > pivot
3. Als k=r, dan
A[k]=pivot
 Merk op:
 Initieel geldt
invariant: triviaal
 Invariant blijft gelden
 Bij terminatie …
Partition na de loop
Allemaal ≤
p
Allemaal >
r
i i+1
 En dan verwisselen we A[i+1] en A[r]
Allemaal ≤
p
10
Allemaal >
i+1
r
Looptijd partition
Partition(A,p,r)
 pivot = A[r];
 i = p – 1;
 for j = p to r – 1 do
 {*}
 if A[j] ≤ pivot
 then
• i ++;
• Verwissel A[i] en A[j]
 Verwissel A[i+1] en
A[r];
 return i+1;
11
 Lineair: Θ(r-p+1)
 Inspectie van
loopstructuur
Quicksort: Twee
CODE EN EERSTE
ANALYSE
12
Datastructuren
Quicksort
Quicksort(A, p, r)
 {Sorteert het deel van de array A[p…r]}
 if p < r
 then
 q = Partition(A, p, r)
 Quicksort(A, p, q-1)
 Quicksort(A, q+1, r)
13
r
p
q
Allemaal ≤
p
14
Allemaal >
r
Hoeveel tijd kost Quicksort?
 In het slechtste geval gaat het erg
langzaam…
 Bekijk een gesorteerde rij:
 We splitsen in stukken van grootte n – 1; 1; 0
 En de volgende keer in stukken van grootte n-2;
1; 0
 Etc.
 Dus: cn+ c(n-1)+ c(n-2)+ c(n-3) + … +3c+2c+c
= c n(n+1)/2 stappen
 Op een gesorteerde rij: Ο(n2) stappen
15
Analyse met recurrente betrekkingen
 Schrijf: T(n) is aantal stappen van Quicksort
op gesorteerd array met n elementen
 T(n)
= T(n-1)+T(0) + Ο(n)
= T(n-1)+ Ο(n)
= Ο(n2)
Andere constantes
Met inductie naar n
16
Quicksort voor aartsoptimisten
 Als we echt geluk hebben, splitst Quicksort
altijd precies middendoor en gaan we in
recursie op twee stukken van hooguit n/2
elementen
 Zelfde analyse als bij Mergesort geeft
Ο(n lg n) tijd
17
log n niveau’s
18
Beste geval analyse van Quicksort met
recurrente betrekkingen
 Stel T(n) is het beste geval van de looptijd
van Quicksort op een array met n
elementen
 T(n) ≤ 2*T(n /2) + O(n) (*)
 T(n) = O(n lg n)
 Volgt uit (*) met inductie
 Zo kan je ook Mergesort analyseren
19
Quicksort voor optimisten (niet
noodzakelijk aartsoptimisten)
 Stel nu dat we altijd verdelingen hebben die
de array splitsen in twee stukken die
verhouding 9 – 1 hebben
 9/10e is gewoon een voorbeeld
 T(n) = T(9n / 10)+ T(n / 10) + Ο(n)
 Recursieboom heeft log10/9 n = Ο(lg n)
lagen
 Per laag Ο(n) dus in zo’n geval eveneens
Ο(n lg n)
 Maar … hoe vaak gebeurt dat?
20
Hoe vaak doen we een goede splitsing?
 In 80% van de gevallen splitsen we 9-1 of
beter…
 Ingewikkelde analyse geeft Ο(n lg n) tijd
gemiddeld over alle mogelijke permutaties
van input als alle getallen verschillend zijn
(doen we niet)
21
Drie
RANDOMIZED QUICKSORT
22
Datastructuren
Hoe zorgen we ervoor dat we heel vaak
goed splitsen
 Idee 1: maak eerst een random permutatie
van de input
 Geeft Ο(n lg n)
 Analyse ingewikkeld
 Idee 2 (beter): gebruik niet A[r] als pivot,
maar gebruik een random element als pivot
 Geeft ook Ο(n lg n)
 Analyse eenvoudiger
 Ietsje sneller
23
Randomized-Partition
Randomized-Partition(A,p,r)
 Kies uniform een random getal i uit de
verzameling {p, p+1, …, r}
 Verwissel A[r] en A[i]
 Partition(A,p,r)
Elk element in A heeft dezelfde kans
om als pivot-element gebruikt te worden
24
Randomized-Quicksort pseudocode
Randomized-Quicksort(A, p, r)
 {Sorteert het deel van de array A[p…r]}
 if p < r
 then
 q = Randomized-Partition(A,p,r)
 Randomized-Quicksort(A, p, q-1)
 Randomized-Quicksort(A, q+1, r)
25
Analyse Randomized Quicksort
 Verschillende manieren om de verwachtte
tijd uit te rekenen
 Netjes: stel recurrente betrekking op, en los
die op (zie o.a. sheets)
 Vandaag: telargument waarbij we kijken
naar “hoe vaak doet een element mee in
een partition”?
26
Maar eerst: slechtste geval
 In het slechtste geval:
 Net als “gewone” Quicksort: gesorteerde rij, en
steeds wordt grootste element als pivot ‘toevallig’
gekozen
 Dus O(n2)
27
Datastructuren
Tijd is O(som partition-lengtes)
 Kijk naar recursieboom
 Totale tijd is O(som van alle lengtes van alle
deelstukken waar we een partitie op doen)
=
 O(som over alle elementen van aantal keren
dat het element in een partitie mee doet)
28
Verwachtte tijd
 Totale verwachtte tijd is O(verwachte som
van alle lengtes van alle deelstukken waar
we een partitie op doen)
=
 O(som over alle elementen van verwachtte
aantal keren dat het element in een partitie
mee doet)
 = n* O(verwachtte aantal keren dat een
element in een partitie meedoet)
29
Afschatten van verwachtte aantal keren
dat een element in een partitie meedoet
 Is O(log n)
 Hoe laten we dit zien?
 Kijk element x, en kijk naar het formaat van
het stuk waar x in zit.
 Begint met formaat n
 Iedere keer een beetje kleiner
 Als formaat 1 is zijn we klaar
 Hoe vaak is het verwachtte aantal keren dat
het kleiner wordt? We laten zien: O(log n)
30
Kansberekening
 Experiment met kans p op succes, 0<p<1
 Wordt r keer uitgevoerd
 Verwachting (E) van aantal successen: pr
 Verwachtte aantal keren tot 1e succes:
1/p
 Verwachtte aantal keren tot re succes: r/p
31
Datastructuren
Kans is ½ dat stuk hooguit ¾ van oude
lengte heeft
 Als we een stuk hebben met r elementen
zijn er r/2 keuzes voor de pivot die zorgen
dat de volgende keer het grootste stuk
hooguit ¾ * r lang is
32
Tellerij klaar
 Hoe vaak kan je n met ¾ vermenigvuldigen totdat je
onder de 1 bent?
 log4/3 n keer = O(log n)
 Wat is het verwachtte aantal keren dat je een
experiment met kans ½ moet doen totdat je s keer
succes hebt?
 2s
 Dus verwachtte aantal keren dat element in partitie
meedoet is hooguit 2 log4/3 n = O(log n) keer
 Dus: verwachtte tijd Quicksort O(n log n)
 Andere analyse (wel in sheets, niet vandaag):
 2n ln n
33
Analyse Randomized-Partition
Slaan we dit jaar over
 Slechtste geval: weer Ο(n2)
 T(n) = max0≤ q≤ n-1 T(q)+T(n-q-1)+Ο(n)
 Verwachtte tijd: analyse doen we hier
aannemend dat alle elementen verschillend
zijn (anders klopt ‘t ook, overigens)
 We doen de analyse hier met behulp van de
sommatiefactormethode
 Eerst: vergelijking looptijd en aantal
vergelijkingen
34
Looptijd vs aantal vergelijkingen
 Stel Quicksort doet X vergelijkingen. Dan gebruikt
het O(n+X) tijd
 Partition doet altijd minstens 1 vergelijking
• Want we roepen Partition alleen aan op stukken met
minstens 2 elementen
 Partition doet O(aantal vergelijkingen in partition) werk
 …
 We gaan nu het verwachtte aantal vergelijkingen
tellen dat Quicksort doet op een array met n
verschillende elementen. Noem dit getal C(n)
35
Technisch detail
 We volgen de analyse uit Concrete
Mathematics. Die gebruikt twee
vergelijkingen per recursieve aanroep extra.
 Deze waardes noemen we D(n).
 D(0)=C(0)=0; als n>0 dan is D(n)>C(n)
 Als we dus voor D(n) een bovengrens
hebben, geeft dat ook een bovengrens voor
C(n)
 Uiteindelijke waarde is dus iets beter (scheelt niet
veel)
36
Aantal vergelijkingen (Randomized)Partition
Partition(A,p,r)
 pivot = A[r];
 i = p – 1;
 for j = p to r – 1 do
 {*}
 if A[j] ≤ pivot
 then
• i ++;
• Verwissel A[i] en A[j]
 Verwissel A[i+1] en
A[r];
 return i+1;
37
 n-1 vergelijkingen op
een array met n
elementen
 Concrete Mathematics
neemt hier n+1
vergelijkingen
Analyse D(n) (1)
 D(0) = 0
 D(1) = 2
 D(n) = n+1 + ????
 Elk van de splitsingen heeft dezelfde kans:
•
•
•
•
•
•
38
0,1,n-1
1,1,n-2
2,1,n-3
…
n-2, 1, 1
n-1, 1, 0
Analyse D(n) (2)
 D(0)= 0
 D(1)= 2
 D(n) = n+1 + 1/n*Σk=0n-1 D(k) + 1/n*Σk=0n-1 D(n-k-
1)
 Elk van de splitsingen heeft dezelfde kans:
•
•
•
•
•
•
0,1,n-1
1,1,n-2
2,1,n-3
…
n-2, 1, 1
n-1, 1, 0
 Of: D(n) = n+1 + (2/n)*Σk=0n-1 D(k) voor n>0
39
2 n −1
D ( n) = n + 1 + ∑ D ( k )
n k =0
n −1
nD(n) = n + n + 2∑ D(k )
2
k =0
Deze hadden we
Maal n nemen
Zelfde vergl. voor n-1
n−2
(n − 1) D(n − 1) = (n − 1) + (n − 1) + 2∑ D(k )
2
k =0
-
nD(n) − (n − 1) D(n − 1) = 2n + 2 D(n − 1)
Vergelijkingen aftrekken
nD(n) = (n + 1) D(n − 1) + 2n
Na vereenvoudigen
40
Stelsel vergelijkingen
 D(0)=0
 nD(n) = (n+1)D(n-1)+ 2n
 Dit stelsel kunnen we met sommatiefactormethode
oplossen
 Idee is: vermenigvuldig formule met sommatiefactor sn
waarbij
• sn = (an-1an-2…a1)/(bnbn-1…b2)
als anD(n)=bnD(n-1)+cn
• Want dan is snbn=sn-1an-1
• En dan krijg je voor E(n)=snanD(n) de formule
E(n)=E(n-1)+sncn
• Wat een somformule voor E en daarna voor D geeft…
41
D(0)=0
nD(n) = (n+1)D(n-1)+ 2n
: dit hadden we
an = n
bn = n+1
cn = 2n
Definitie toepassen:
Alles
maal sn:
(n − 1) ⋅ (n − 2) ⋅  ⋅1
2
=
(n + 1) ⋅ n ⋅  ⋅ 3
(n + 1)n
2
2
2
nD(n) =
2n
(n + 1) D(n − 1) +
(n + 1)n
(n + 1)n
(n + 1)n
Def.:
(*) en (**)
geven:
42
sn =
2
2 D ( n)
E ( n) = sn an D ( n) =
∗ n ∗ D ( n) =
(n + 1)n
n +1
4
E (n) = E (n − 1) +
n +1
(*)
(**)
4
E (n) = E (n − 1) +
n +1
n
dus
4
E ( n) = ∑
k =1 k + 1
We hadden
Want E(0)=0
2 D ( n)
E ( n) =
n +1
n
dus
43
1
D(n) = 2(n + 1)∑
k =1 k + 1
Aantal vergelijkingen randomized
quicksort
n
1
H n = ∑ ≈ ln n
k =1 k
n
1
D(n) = 2(n + 1)∑
k =1 k + 1
Randomized-Quicksort doet verwacht
ongeveer 2(n+1)ln n vergelijkingen
44
Nieuw onderzoek
 Recent werk: quicksort met meer dan 1
pivotelement ... Is soms nèt iets sneller
 “Dual Pivot Quicksort”
45
Datastructuren
ADT versus Datastructuur
 Datastructuur
 is een systematische manier van organiseren van data
en toegang verlenen tot diezelfde data.
 Abstract data type
 is een model van een datastructuur waarin
gespecificeerd is:
• type van de data
• operaties ter ondersteuning van de datastructuur
• de types van de parameters van deze operaties
 Een abstract data type concentreert zich op functionaliteit,
niet op tijd.
 Vandaag: Heap (is ADT), Array-implementatie van Heap
46
Datastructuren
Heap
 “Hoop”, zoals in “een steenhoop”
 Datastructuur, gebruikt voor sorteren en
priority queue
 Een heap is eigenlijk een boom, maar kan
heel efficient in een array worden
weergegeven
 Datastructuren voor “echte” bomen komen
later
47
Heap
 “Bijna volledige binaire boom”
 Vervult de “heap-eigenschap”
 Wat bedoelen we hiermee?
48
Binaire boom
 Binaire boom:
 Iedere knoop heeft 0,
1 of 2 kinderen
 Volledige binaire boom:
 Behalve op het
onderste niveau heeft
elke knoop 2 kinderen
 Een knoop kan hebben:
 Ouder (PARENT)
 Linkerkind (LEFT)
 Rechterkind (RIGHT)
49
Bijna volledige binaire boom
 Alle niveau’s helemaal
gevuld, behalve ‘t
onderste dat een eindje
van links af gevuld is,
en daarna niet meer
 Volledige bb mag ook
50
Twee termen
 Diepte van knoop: afstand naar wortel
 Hoogte van knoop x: maximale afstand
naar blad onder x
51
Heap-eigenschap
 Elke knoop x in de heap heeft een waarde
A[x]
 Max-heap eigenschap: voor alle knopen i
(behalve natuurlijk de wortel van de boom)
geldt: A[PARENT(i)] ≥ A[i]
 Min-heap eigenschap: voor alle knopen i
(behalve natuurlijk de wortel van de boom)
geldt: A[PARENT(i)] ≤ A[i]
52
16
10
14
8
2
4
7
9
3
1
Max-heap
53
Heapsort
 Gebruikt de Heap datastructuur met
implementatie in array
 Heap
54
Implementatie van een heap
16 1
10 3
14 2
4 8
8 2
7 5
6 9
3 7
4 1
10
9
16 14 10 8 7 9 3 2 4 1
55
Implementatie van een heap
 Gebruik een array
 A[1] is de wortel
 A[2], A[3] de
achteenvolgende
elementen op hoogte 1
 A[4], A[5], A[6], A[7]
voor hoogte 2,
 A[2r], … A[2r+1-1] voor
hoogte r
56
 PARENT(i)
 Return  i/2  ;
 LEFT(i)
 Return 2i;
 RIGHT(i)
 Return 2i+1;
Array implementatie
16 1
10 3
14 2
4 8
8 2
7 5
6 9
3
4 1
10
9
16 14 10 8 7 9 3 2 4 1
57
7
 PARENT(i)
 Return  i/2  ;
 LEFT(i)
 Return 2i;
 RIGHT(i)
 Return 2i+1;
“Operaties” op Max-Heap
 Build-Max-Heap
 Maak een heap van een ongeordende array elementen
 Max-Heap-Insert
 Voeg een nieuw element toe aan een heap
 Heap-Extract-Max
 Haal het grootste element uit de heap en lever dat op
 Heap-Increase-Key
 Verhoog de waarde van een element
 Heap-Maximum
 Lever de waarde van het grootste element op (zonder
iets te veranderen)
 NB: Notatie boek is wat slordig (verwart ADT en
implementatie, maar ik volg ‘m toch)
58
Min-heaps
 Net als Max-heaps met min en max (etc.)
omgedraaid
59
Als we deze operaties geimplementeerd
hebben, kunnen we sorteren
 Build-Max-Heap(A)
 For i=0 to n-1 do
 B[n-i] = Heap-Extract-Max(A)
60
Belangrijke subroutine: Max-Heapify
 Max-heapify(A,i)
 {Input-aanname: de binaire boom met
wortel LEFT(i) en de binaire boom met
wortel RIGHT(i) zijn max-heaps}
 {Output: permutatie, zodat de binaire boom
met wortel i is een max-heap}
61
16 1
10 3
4 2
4 8
8 2
7 5
6 9
3
7
5 1
10
9
Idee: als i groter (≥) is dan beide
kinderen: OK, klaar
Anders, verwissel met grootste kind
en ga dan corrigeren op de plek van ‘t
grootste kind
62
16 1
10 3
4 2
4 8
8 2
5
7 5
1
6 9
3
7
16 1
10
10 3
8 2
4 4
8 2
63
5
7 5
1
10
6 9
3
7
Max-heapify
Max-Heapify(A,i)
 links = LEFT(i)
 rechts = RIGHT(i)
 if (links ≤ heap-size[A] and A[links] > A[i])
 then grootste = links
 else grootste = i
 if (rechts ≤ heap-size[A] and A[rechts] >
A[grootste])
 then grootste = rechts
 if (grootste ≠ i)
 then
 Verwissel A[i] en A[grootste]
 Max-Heapify(A,grootste)
64
Analyse Max-Heapify
 Correct?
 Looptijd: O(diepte van i)
 De diepte van een knoop is nooit meer dan
log n, als heap-size(A)=n
 Dus: O(log n)
65
Build-Max-Heap
Build-Max-Heap(A)
 {Input: ongesorteerde rij getallen
A[1…lengte(A)]}
 {Output: A is een permutatie van input die
aan max-heap eigenschap voldoet}
66
Build-Max-Heap
Build-Max-Heap(A)
 {Input: ongesorteerde rij getallen
A[1…lengte(A)]}
 {Output: A is een permutatie van input die
aan max-heap eigenschap voldoet}
 heap-size[A] = lengte(A);
 for i=  lengte(A)/2  downto 1 do
 Max-Heapify(A,i)
That’s all en
‘t klopt ook nog!
67
Correctheid Build-Max-Heap
 Invariant: aan het begin
van de for-loop is elke
knoop i+1, … n de wortel
van een max-heap
 Initieel: klopt, want
boompjes van 1 knoop
 Onderweg: vanwege MaxHeapify… (bespreken
details)
 Terminatie: leuk, want
i=0, dus knoop 1 is wortel
van max-heap, dus hele
array is max-heap
68
for i=  lengte(A)/2 
downto 1 do
Max-Heapify(A,i)
Tijdsanalyse Build-Max-Heap
 Eenvoudige analyse geeft O(n log n)
 Voor iedere i tussen 1 en n/2 doen we O(log n)
werk
 Meer precieze analyse geeft O(n)
 Werk voor knoop i is O(hoogte(i))
 De hoogte van de boom is  log n  (basis 2)
 Er zijn n / 2h+1 knopen met hoogte h in de boom
 Gebruik dat
∞
h
=2
∑
h
h =0 2
Details op bord
69
Heapsort
 Build-Max-Heap(A)
 for i = lengte(A) downto 2 do
 {A[1] is het maximum}
 {A[1…heap-size[A]} is een heap, de elementen
na heap-size[A] zijn gesorteerd maar niet langer
in de heap}
 {Invariant: i = heapsize[A]}
 Verwissel A[1] en A[i];
 Heapsize[A] --;
 Max-Heapify[A];
70
Analyse
 Correct, want …
 O(n log n) tijd want…
71
Next:
 Andere operaties op heaps
 ...
72