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
© Copyright 2024