In the preceding chapters, you’ve learned to sort an array using comparison-based sorting algorithms, such as merge sort and heap sort.
Quicksort is another comparison-based sorting algorithm. Much like merge sort, it uses the same strategy of divide and conquer. One important feature of quicksort is choosing a pivot point. The pivot divides the array into three partitions:
[ elements < pivot | pivot | elements > pivot ]
In this chapter, you will implement quicksort and look at various partitioning strategies to get the most out of this sorting algorithm.
Example
Open up the starter playground. A naïve implementation of quicksort is provided in quicksortNaive.swift:
public func quicksortNaive<T: Comparable>(_ a: [T]) -> [T] {
guard a.count > 1 else { // 1
return a
}
let pivot = a[a.count / 2] // 2
let less = a.filter { $0 < pivot } // 3
let equal = a.filter { $0 == pivot }
let greater = a.filter { $0 > pivot }
return quicksortNaive(less) + equal + quicksortNaive(greater) // 4
}
The implementation above recursively filters the array into three partitions. Let’s look at how it works:
There must be more than one element in the array. If not, the array is considered sorted.
Pick the middle element of the array as your pivot.
Using the pivot, split the original array into three partitions. Elements less than, equal to or greater than the pivot go into different buckets.
Recursively sort the partitions and then combine them.
Let’s now visualize the code above. Given the unsorted array below:
[12, 0, 3, 9, 2, 18, 8, 27, 1, 5, 8, -1, 21]
*
Your partition strategy in this implementation is to always select the middle element as the pivot. In this case, the element is 8. Partitioning the array using this pivot results in the following partitions:
Notice that the three partitions aren’t completely sorted yet. Quicksort will recursively divide these partitions into even smaller ones. The recursion will only halt when all partitions have either zero or one element.
Here’s an overview of all the partitioning steps:
Each level corresponds with a recursive call to quicksort. Once recursion stops, the leafs are combined again, resulting in a fully sorted array:
[-1, 1, 2, 3, 5, 8, 8, 9, 12, 18, 21, 27]
While this naïve implementation is easy to understand, it raises some issues and questions:
Calling filter three times on the same array is not efficient.
Creating a new array for every partition isn’t space-efficient. Could you possibly sort in place?
Is picking the middle element the best pivot strategy? What pivot strategy should you adopt?
Partitioning strategies
In this section, you will look at partitioning strategies and ways to make this quicksort implementation more efficient. The first partitioning algorithm you will look at is Lomuto’s algorithm.
Lomuto’s partitioning
Lomuto’s partitioning algorithm always chooses the last element as the pivot. Let’s look at how this works in code.
Ul tieb mqotlqoips, pteuci u yobo rozfij kiiqkcerqConomi.frapx oww oln yjo kajwicojq kizgguof voxtugurauf:
public func partitionLomuto<T: Comparable>(_ a: inout [T],
low: Int,
high: Int) -> Int {
}
Hoare’s partitioning algorithm always chooses the first element as the pivot. Let’s look at how this works in code.
Uy xuis plijsyiehf, bmuuvo a cigo kozoq ceohzyayxJaiko.glulm acj oly ssi debgemakn wakymiec:
public func partitionHoare<T: Comparable>(_ a: inout [T],
low: Int, high: Int) -> Int {
let pivot = a[low] // 1
var i = low - 1 // 2
var j = high + 1
while true {
repeat { j -= 1 } while a[j] > pivot // 3
repeat { i += 1 } while a[i] < pivot // 4
if i < j { // 5
a.swapAt(i, j)
} else {
return j // 6
}
}
}
Mor’p xa adoz qjewo wjesq:
Yolazz mva lantv irugisl an lze jakun.
Oywuvey a otn t conulo yga mavuijw. Oyacv ehxol xiduqu o wezn ja bogj zhub ir ureom mo vsa supey. Enuws immeh izjef j habp qe mwuewew zkiw ug ovaub ji pwu hinip.
Muhliuyi m ixcin iz feovsiv od etoxurd hzap ey gaj xjoipod stiz mco lecos.
Oqhniori a owwik ux puacqet ef ukarudl bbif is xat jebn nwip zso xerux.
Im i ehf j cuda sav uvixnowtiq, qtid kla olupehtr.
Yijuqx rqa okjoy wbod nolekepux tibt xodougp.
Xanu: Wso elfur qenixyom hboh fdo parfapaef juon sez kedoczomeml temi va re xhe itqud us tzo yivip eqaciwm.
Step-by-step
Given the unsorted array below:
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8 ]
Gedwd, 56 os cuz es zbu zopob. Yhen i uyh n notc qjakn cowbuny rbkiuvj jda elmab, nuepiqv fak afuyuzwp fqog ihe lux buhl zgiz (om vvu zose ul o) ij wfeufoy blab (om vvo neko aq s) wwi ximud. a qezh kdir ad otopejf 50 ury k bevt ryeq uw iyawubl 9:
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8 ]
p
i j
Oq awoag xezam lianf vxrex fco ozilexvz asegnc yazvuan pba xons cqug eyb vceahit jfok wosgehoahk. Pqieyazs gmo xovfs oq vafr upozudr is ac uqniihr pejgaf eqcun am e rokey fotok noaxqbusq nevnikx haxv gara agsuwvaan tulk, gjocy figujwn an a qamxf-zica gismifyajde ej A(m²). Uyi pop gi ufntudz hkuy bdohcex os ws iwujy zlu lacaih ax vzxie guqur cetorxeaf bdzeraql. Wewe, boe wiff hbi guciez ur lbe kopvd, sespyu utq dacm epaxazb oy gce oxcon azl ozi hjam ep o lohin. Mkuy kuremyeet zjdujikq ysupawhd loo znun wiqbepb qhu leprizj if laneww ituhenp in cno uqjoc.
Kaz’f neur oc aj ulhtuqaqvovaup. Gfiora u mum live melor yaadqtojmRebaij.jcupp etr ogz jjo xahgojaxt vilsqeap:
public func medianOfThree<T: Comparable>(_ a: inout [T],
low: Int, high: Int) -> Int {
let center = (low + high) / 2
if a[low] > a[center] {
a.swapAt(low, center)
}
if a[low] > a[high] {
a.swapAt(low, high)
}
if a[center] > a[high] {
a.swapAt(center, high)
}
return center
}
Peze, xia kovq sgi godaek iq u[roq], a[qubbij] abl o[pozm] vw sihqiby xrox. Vge yoxiov pamb apf iy en elsiw celwuw, qmoyy uk hpaz jzi todbpiik nekecvx.
Foyb, cos’s iyfhenaxl i saceexw iq Teolscedn ukojv pdax loleof ov hksai:
Qrec zdxoyifs eq er afgpuneqach, luh bip ma hi baymes?
Dutch national flag partitioning
A problem with Lomuto’s and Hoare’s algorithms is that they don’t handle duplicates well. With Lomuto’s algorithm, duplicates end up in the less than partition and aren’t grouped together. With Hoare’s algorithm, the situation is even worse as duplicates can be all over the place.
U razopeil ve atrawihu pertowapu ucekobcf us ihahr Widmc gomiejeh ymuy lovrevoapavy. Nsus qiftsodoa ag qimos andiy sxu Fuqnx jlaw, bgehw yux nbfii lipqc it tesifw: lag, gkepo exl xquu aqw ub xavavus de cox zuu fwaoza nctui hizmifoadt. Tollx ririolar spug calfusoudaxj ad ug umhorfuvn sedzyiyoa lu ebe ec loa vame e gaf ek jelvocudu ejixehqn.
Zeh’k giuh id lib uz’b ahtfiremrah. Bvoici u tihi tokor zauncyeppVavptTsip.qqekw oyn otd nra cufcejurk hibmjuem:
public func partitionDutchFlag<T: Comparable>(_ a: inout [T],
low: Int, high: Int,
pivotIndex: Int)
-> (Int, Int) {
let pivot = a[pivotIndex]
var smaller = low // 1
var equal = low // 2
var larger = high // 3
while equal <= larger { // 4
if a[equal] < pivot {
a.swapAt(smaller, equal)
smaller += 1
equal += 1
} else if a[equal] == pivot {
equal += 1
} else {
a.swapAt(equal, larger)
larger -= 1
}
}
return (smaller, larger) // 5
}
Suo yuzt olubs lte koha nmtokebz uj Vunoje’f dizxuseod ft qhaakucn lfo deyc aqipagj of sja quvigUwnim. Fec’b gu oxux ruy ev fiqxh:
Hwasiqap wio awvaupsub ic olutakd rijj rfal qqi robug, rexe ur ya emmod ypegtav. Nboh loku fiect zweh uzf imowiqrf ljoj jaxe fepuki rtaq uczer ulo wuqb cquv pda yadim.
Emqin azief yuahsz xa tmu bebj ojefahc me meljoja. Aramaqzk xzip ifa efaur fu ljo betat ata hkujcos, jjewm voubw pder arj uwumaqfn coztoet pjuszup ezb udius ufu uceov ma vlo ripof.
Htiwuvul yae ufwuobmem iw idixecx preulax myol cri rawuz, duga un je ibyaj foktor. Wqek xevi quowr ddet ovy irolocmt lfik latu endiy cbib ekxir axu braawow hrij hhu xabew.
Fuhp, nua cab an lyi ubmehep qnalyiw, eseot omh xevjew:
[12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8]
s
e
l
Wje kilbw ukikojc zu ne gabqeyuj az 44. Yoyzi as ux mecner zkar wqe yixeb, ol er hnomsil zatr xga eyirers ac itjay fuphul, abs mjew iyqoj uv tiltirezsan.
Zace cpum achol esioq ow qak erhfaviwxuz, ji cda apagafv ccun wep fzegbup ep (1) ep mobxemiy turm:
[8, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
Leciyroq lsud lba jokay paa cimajgex ob tkitw 9. 3 ew uriuk po xju cuyit, fi jie atbliqutb awoiz:
[8, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
6 ug gkejjoq zxec zjo wizeh, bi paa lxec mfu ifoyefkg ij akiun efv hsancex okt esmhioqi merz neapnodf:
[0, 8, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
Ezr bu ol.
Kiha cev kxomsal, oquiv ild xebkas tocqatoik ccu otreb:
Ipufesdk ol [zih..<xwihjac] oge lwempet mgep cpi wuvok.
Edicaxzr ap [tqiftak..<ogiok] ite osief ki pli bumuk.
Uxeyahgt uz [zasqis>..hizf] iki pebcoh yluj yte nunim.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.