Quicksort

ID1020: Quicksort
Dr. Jim Dowling
jdowling@kth.se
kap 2.3
Slides adapted from Algoritms 4th Edition, Sedgewick.
Quicksort
• Grundläggande metod.
- Blanda array:n.
- Partitioner så att för något värde j
• element a[j] är på rätt plats
• inget element som är större ligger till vänster om j
• inget element som är mindre ligger till höger j
- Sortera alla subarrayer rekursivt.
2
Tony Hoare
- Hoare uppfann quicksort för att översätta från ryska till engleska.
- Implementerade för första gången i Algol 60 (med rekursion).
Tony Hoare
1980 Turing Award
Communications of the ACM (July 1961)
“ There are two ways of constructing a software design: One way is to make it so simple that there
are obviously no deficiencies, and the other way is to make it so complicated that there are no
obvious deficiencies. The first method is far more difficult. ”
3
Quicksort partitioning demo
• Upprepa tills i och j pekarna korsar varandra.
- Skanna i från vänster till höger så länge (a[i] < a[lo]).
- Skanna j från höger till vänster så länge (a[j] > a[lo]).
- Swappa a[i] med a[j].
K
R
lo
i
A
T
E
L
E
P
U
I
M
Q
C
X
O
S
j
4
Quicksort partitioning demo
• Upprepa tills i och j pekarna korsar varandra.
- Skanna i från vänster till höger så länge (a[i] < a[lo]).
- Skanna j från höger till vänster så länge (a[j] > a[lo]).
- Swappa a[i] med a[j].
• När pekarna korsar.
- Swappa a[lo] med a[j].
E
lo
C
A
I
E
K
L
P
j
U
T
M
Q
R
X
O
S
hi
partitioned!
5
Quicksort: Java kod för partitionering
private static int partition(Comparable[] a,
int lo, int hi)
{
int i = lo, j = hi+1;
while (true)
{
while (less(a[++i], a[lo]))
if (i == hi) break;
while (less(a[lo], a[--j]))
if (j == lo) break;
if (i >= j) break;
exch(a, i, j);
}
exch(a, lo, j);
return j;
}
hitta elementet på västra sidan som ska swappas
hitta elementet på högra sidan som ska swappas
kontrollera om pekarna korsar
swappa
swappa med partitioneringselement
returnera indexet av elementet som vi nu vet
är på rätt plats
6
Quicksort partitionering trace
7
Quicksort: Java implementation
public class Quick
{
private static int partition(Comparable[] a, int lo, int hi)
{ /* see previous slide */ }
public static void sort(Comparable[] a)
{
StdRandom.shuffle(a);
sort(a, 0, a.length - 1);
}
blandning behövs för
prestandagarantier
private static void sort(Comparable[] a, int lo, int hi)
{
if (hi <= lo) return;
int j = partition(a, lo, hi);
sort(a, lo, j-1);
sort(a, j+1, hi);
}
}
8
Quicksort trace
Quicksort animation
50 poster i slumpmässiga ordning
algoritm position
i ordning
nuvarande subarray
inte i ordning
http://www.sorting-algoritms.com/quick-sort
10
Quicksort: implementation detaljer
• Partitionering in-place (på plats). Om vi använder en extra, partitioneringen
blir lättare (och stabil), men det är inte värt kostnaden.
• Att sluta iterera (loopa). Att kontrollera när pekarna korsar är svårare än vad
man tror.
• Duplikat (lika) nycklar. När det finns duplikat nycklar, är det (kontraintuitivt)
bättre att sluta skanna på nycklar som är lika partitioneringselementets
nyckel.
• Bevara randomiseringen. Vi behöver blanda array:n för att kunna ge
prestandagarantier.
• Ett ekvivalent alternativ. Välj ett slump partitioneringselement i alla
subarrayer.
11
Quicksort: empirisk analys (1961)
• Körtids- mätningar och -estimeringar:
- Algol 60 implementation.
- National-Elliott 405 dator.
sorting N 6-word items with 1-word keys
Elliott 405 magnetic disc
(16K words)
12
Quicksort: empirisk analys
• Uppskattningar av körtider:
- Home PC exekverar 108 jämförelser/sekund.
- Supercomputer exekverar 1012 jämförelser/sekund.
insertion sort (N2)
mergesort (N log N)
quicksort (N log N)
computer
thousand
million
billion
thousand
million
billion
thousand
million
billion
home
instant
2.8 hours
317 years
instant
1 second
18 min
instant
0.6 sec
12 min
super
instant
1 second
1 week
instant
instant
instant
instant
instant
instant
• Läxa 1. Bra algoritmer är bättre än supercomputers.
• Läxa 2. Framstående algoritmer är bättre än bra algoritmer.
13
Quicksort: best-case analys
• Best case. Antalet jämförelser är ~ N lg N.
input värden
blanda värden
14
Quicksort: worst-case analys
• Worst case. Antalet jämförelser är ~ ½ N 2 .
initial values
random shuffle
15
Quicksort: average-case analys
(förväntad exekveringstid)
• Sats. Medelantalet jämförelser CN för att köra quicksort på en array med N
unika nycklar är ~ 2N ln N (antalet byten är ~ ⅓ N ln N ).
• Bevis. CN satisfierar rekurrensrelationen C0 = C1 = 0 och for N ≥ 2:
partitionering
vänster
höger
partitioneringssannolikhet
- Multiplicera båda sidor med N och samla termerna:
- Ta bort från denna ekvation samma ekvation för N - 1 fallet:
- Flyttar runt termerna och dividera med N (N + 1):
16
Quicksort: average-case analys
- Upprepa att applicera ekvationen ovan:
subsituera föregående ekvation
föregående ekvation
- Approximera summan med hjälp av en integral:
- Slutligen, önskade resultatet är:
17
Quicksort: sammanfattning av prestandaegenskaper
• Quicksort är en randomiserad algoritm.
- Det finns en garanti för korrekthet.
- Körtiden beror på slump blandningen.
• Average case. Förväntade antal jämförelser är ~ 1.39 N lg N.
- 39% mer jämförelser än mergesort.
- Snabbare än mergesort i praktiken för att man kopierar mindre mängder
av data.
• Best case. Antalet jämförelser är ~ N lg N.
• Worst case. Antalet jämförelser är ~ ½ N 2.
Quicksort egenskaper
• Sats. Quicksort är en in-place (på plats) sorteringsalgoritm.
• Bevis.
- Partitionering: konstant extra plats.
- Djupet av rekursion: logaritmisk extra plats (med hög sannolikhet).
• Sats. Quicksort är inte stabil.
• Bevis genom motexempel.
i
j
0
1
2
3
B1
C1
C2
A1
1
3
B1
C1
C2
A1
1
3
B1
A1
C2
C1
0
1
A1
B1
C2
C1
1
Quicksort: förbättringar
• Insertion sort på små subarrayer.
- T.o.m. quicksort har för mycket overhead för mycket små subarrayer.
- Brytpunkt till insertion sort för  10 element.
private static void sort(Comparable[] a, int lo, int hi)
{
if (hi <= lo + CUTOFF - 1)
{
Insertion.sort(a, lo, hi);
return;
}
int j = partition(a, lo, hi);
sort(a, lo, j-1);
sort(a, j+1, hi);
}
2
Quicksort: förbättringar
• Median av ett stickprov.
- Bäst val av partitioneringselement = median.
- Approximera riktiga medianen genom att beräkna medianen av stickprovet.
- Median-av-3 (slumpmässiga valde) element.
~ 12/7 N ln N jämförelser(14% mindre)
~ 12/35 N ln N byten (3% mer)
private static void sort(Comparable[] a, int lo, int hi)
{
if (hi <= lo) return;
int median = medianOf3(a, lo, lo + (hi - lo)/2, hi);
swap(a, lo, median);
int j = partition(a, lo, hi);
sort(a, lo, j-1);
sort(a, j+1, hi);
}
2
Duplikat nycklar
• Ofta, syftet med sortering är att samla ihop element med samma nycklar.
- Sortera befolkningen efter ålder.
- Ta bort duplikat från en e-postlista.
- Sortera jobbansökan efter sökandens högskolebetyg.
• Vanliga egenskaper av sådana applikationer.
- Enorm array.
- Ett få antal nycklar.
nyckel
2
Quicksort med duplikat nycklar
Element med samma nyckeln (duplicate keys)
• Quicksort med duplikat nycklar. Algoritmen kan ta kvadratisk tid om inte
partitioneringen slutar när man jämför lika nycklar!
S T O P O N E Q U A L K E Y S
swap
om vi inte slutar vid
lika nycklar
om vi slutar vid
lika nycklar
• Vissa implementationer (både i böcker och i industri) tar kvadratisk tid när
indata innehåller många element med samma nyckeln.
2
Partitioneringselementet när
vi partitionerar med duplikat
• Vad hamnar partitioneringselementet när vi partitionerar array:n nedan med
Quicksort?
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
• A.
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
• B.
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
A
• C.
2
Att partitionera en array med bara lika nycklar
2
Duplikat nycklar: problemet
• Recommenderad. Sluta skanna vid element lika med
partitioneringselementet.
• Resultat.
~ N lg N compares when all keys equal.
B A A B A B C C B C B
A A A A A A A A A A A
• Misstag. Fortsätta skanna även om man ser element som är lika
partitioneringselementet.
• Resultat. ~ ½ N 2 jämförelser när alla nycklar är lika.
B A A B A B B B C C C
A A A A A A A A A A A
• Mål. Lägg alla element som är lika partitioneringselementet på rätt plats.
A A A B B B B B C C C
A A A A A A A A A A A
2
3-väggs partitionering
• Mål. Partitionera array:n i tre lika stora delar så att:
- Elementet mellan lt och gt är likt partitioneringselementet.
- Inga element som är större ligger till vänster om lt.
- Inga element som är mindre ligger till höger om gt.
•
2
Dijkstra 3-väggs partitionering
- Låt v vara partitioneringselementet a[lo].
- Skanna med i från vänster till höger.
• (a[i]
< v): byt a[lt] med a[i]; inkrementera både lt och i
• (a[i]
> v): byt a[gt] with a[i]; dekrementera gt
• (a[i] == v): inkrementera i
lt i
P
gt
A
B
X
W
P
P
V
lo
P
D
P
C
Y
Z
hi
invariant
3
Dijkstra 3-väggs partitioning demo
- Låt v vara partitioneringselementet a[lo].
- Skanna med i från vänster till höger.
• (a[i]
< v): byt a[lt] med a[i]; inkrementera både lt och i
• (a[i]
> v): byt a[gt] with a[i]; dekrementera gt
• (a[i] == v): inkrementera i
lt
A
B
C
D
P
lo
gt
P
P
P
P
V
W
Y
Z
X
hi
invariant
3
Dijkstra's 3-väggs partitioning: trace
3
3-väggs quicksort: Java implementation
private static void sort(Comparable[] a, int lo, int hi)
{
if (hi <= lo) return;
int lt = lo, gt = hi;
Comparable v = a[lo];
int i = lo;
while (i <= gt)
{
int cmp = a[i].compareTo(v);
if
(cmp < 0) exch(a, lt++, i++);
else if (cmp > 0) exch(a, i, gt--);
else
i++;
}
sort(a, lo, lt - 1);
sort(a, gt + 1, hi);
}
3
3-väggs quicksort: visual trace
3
Duplikat nycklar: lägre gräns
• Sortering lägre gräns. Om det finns n unika nycklar och elementet i
har xi duplikat, alla jämförelse-baserade sorteringsalgoritmer behöver minst
N lg N när alla nycklar är unika;
Linjärtid när det finns bara ett konstant antal unika nycklar.
jämförelser i värsta fallet.
proportionellt mot lägre gränsen
• Sats. [Sedgewick-Bentley 1997]
• Quicksort med 3-väggs partitionering är entropy-optimal.
• Bevis. [inte i kursens omfattning]
• Slutsats. Quicksort med 3-väggs partitionering reducerar körtiden från
linearithmic till linjär för många applikationer.
3
Sortering sammanfattning
inplace?
selection
✔
insertion
✔
shell
✔
merge
quicksort
3-väggs
quicksort
?
stable?
✔
✔
best
average
worst
remarks
½N2
½N2
½N2
N exchanges
N
¼N2
½N2
N log3 N
?
c N 3/2
N lg N
N lg N
½ N lg
N
✔
N lg N
2 N ln N
½N2
✔
N
2 N ln N
½N2
N
N lg N
N lg N
✔
✔
use for small N
or partially ordered
tight code;
subquadratic
N log N guarantee;
stable
N log N probabilistic guarantee;
fastest in practice
improves quicksort
when
duplicate keys
holy sorting grail
3
Vilken sorteringsalgoritmen ska man använda?
• Det finns många (andra) sorteringsalgoritmer att välja mellan:
sorts
algoritms
elementary sorts
insertion sort, selection sort, bubblesort, shaker sort, ...
subquadratic sorts
quicksort, mergesort, heapsort, shellsort, samplesort, ...
system sorts
dual-pivot quicksort, timsort, introsort, ...
external sorts
Poly-phase mergesort, cascade-merge, psort, .…
radix sorts
MSD, LSD, 3-way radix quicksort, …
parallel sorts
bitonic sort, odd-even sort, smooth sort, GPUsort, ...
3
Vilken sorteringsalgoritmen ska man använda?
• Applikationer har olika krav:
- Stabil?
- Parallel?
- In-place?
- Deterministisk?
- Duplikat nycklar?
- Olika typer av nycklar?
- Länkadlista eller array?
- Stor eller små element?
- Finns array:n i slumpmässig ordning?
- Prestandagarantier?
• Är ”system sort” bra nog i vanliga fall?
• Ja, det brukar vara snabb nog.
3
System sortering i Java 7
• Arrays.sort().
- har en metod för objekt som är Comparable.
- har en överlagrad metod för alla primitiva typer.
- har en överlagrad metod för använda med en Comparator.
- har överlagrada metoder för att sortera subarrayer.
• Algoritmer.
- Dual-pivot quicksort för primitiva typer.
- Timsort (mergesort variant) för referenstyper.
• Varför använder man olika algoritmer beroende på om objekt är primitiva
eller referens typer?
3