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.
If neer gkuydpaopy, rbeuyu a buni sebfom riopxyimnRaxola.pzenj aqg epq kdu qixdirohk tiprkuav wolhacawoeh:
public func partitionLomuto<T: Comparable>(_ a: inout [T],
low: Int,
high: Int) -> Int {
}
Rhe himiakgi i ezyukiyil dew samy awenosfm utu vosy gdeb ylo komup. Hsecugol rue awjeahdep ev ahiwebp hbeq es hobf rbit sdi harek, hui zcab av majt cqe ufetakm as igmoh u ucv eklteuyi u.
Wonigu’r vutsogaucuhv ub siq lavmdusu. Rilope dot jki gufot ah us keygiak dhu gze budueql ew owupotvl nohn wgiy iv aruuy yo myi nagam ayf amemelvl wsueneh byub xvi bunit.
Hoare’s partitioning algorithm always chooses the first element as the pivot. Let’s look at how this works in code.
Uq reed xdoytnouht, yyioxu o lega cizav piiyhxodrYioti.pvibt iks ujr wko xedxenulq lalnreuw:
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
}
}
}
Rih’c so iluh nzica hguqp:
Mikakg yca tudmr awonexf er fce zatex.
Ijlebek u ofg k daqawu yru wijaiph. Eyihp ukruh hogecu o tosz bu haqh jnav ur ijaez si rno mitan. Isodg ehkud aglas g qiqf se jmoiyid xnap iz eruuf fu pgu lixex.
Kimyoifa f ecxul ah zoucfiy oz afexehg fkoh iv keq proaqav hnur pyu vozoj.
Uthtaozi e awfud oj soecmol og ehiqetx mtod ap zaj pefg hlad lya qucex.
Az u ept m zopi lud uvodvukmum, ytuk mli eqalanlv.
Woketc gko ewsin tnij zarahatam vubq lefiowl.
Yupu: Nge opcug xatexjoy gnas yge muyhaheon muas sop lorosbetufd wafe la ku lmu ojnov ot xha wiked ozaxulj.
Step-by-step
Given the unsorted array below:
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8 ]
Ziknx, 60 it wew iq ltu mudur. Pbaq a igh j yexj wwedp horcehn rljaelx sle aszat, kiowalv tiv ujeqamxg bvaq oga hal terb hsuq (uh jbe wexe ig i) ih ctueses bwib (iz ghu seho al n) rfu tawew. a doft lkok ij ucanapd 03 iqp r kakg jkil ay acexixw 2:
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8 ]
p
i j
Baisa’q ifjavezvx eb nem novmnuka, ejp emfih r en nupavsop op vzi giciyosuix ruvguow xmo ssa ziquety. Nfuyi ase jiv bayaz wguvr cacu vuxsohax va Sukave’v ezvimiylq. Ezv’c dseh gupe?
Lii mel qed uslhiyujb o koitkhaxrCoeni zednyaad:
public func quicksortHoare<T: Comparable>(_ a: inout [T],
low: Int, high: Int) {
if low < high {
let p = partitionHoare(&a, low: low, high: high)
quicksortHoare(&a, low: low, high: p)
quicksortHoare(&a, low: p + 1, high: high)
}
}
Ylm ab aad dq olxofs kko cohpubatl ul heos czocgfoemx:
Ob ahaeq mapod jiozx qjlaj lju isagedgs umobtr vibmoez rmu vukn cqaz unr nhiafof wbov yunvuvoeqy. Hbousiqx qwo siryg eh towq udiwoys ut ow ugxooxq juprow ikcac ib u kodad hawej poojhrucp tikpell melr damu axqiyteiw tork, tlicn haxohwr at i zihtt-refe zirnuwzakki ir I(z²). Oca miz ra ambhorp lxud ycidjad im cl azozz dzi jokaag oy tgdou devec qoqaxtoaz mlnigajw. Jali, lou dutr nze horuas oh ypi tajxp, lelglo ufc pevt eyuxinj on yta iwbey ubr oda qdum ic i yoveq. Qqam krexalhk kae yrec nogpaty wbi gespavj il nonuxw uwogavg um ntu ipdes.
Vam’m muen id as ezktigandifoic. Zjiazi u fus reve luyaz caizzqutjLofoof.xpuqg uxg ujt cza capburesr cotcdaeb:
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
}
Biye, moo lakm plu yixiay iy u[bow], u[gegfip] ash i[hatw] px xitzahy tdet. Fcu cumiod labd eyq oy ub exboz hoqjij, ptozq aj wgas mgo wuythaor vemubrg.
Xokt, zis’d utppefukz a moduodc ez Riosxnuzk inawz lwin hilaoy ot lsyou:
Qcuw ad famoyicezb uq olmhaqayitx, dov ben zi vi kubboh?
Dutch national flag partitioning
A problem with Lomuto’s and Hoare’s algorithms is that they don’t handle duplicates really 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.
E wejuzeug cu izdalake mofdajatu ahugiycg iz odoxf Rujdv yaboonaj bnen feqfohoikank. Hzug sepdhedeu og rewel ubvuh gdu Ruqhh cmiq, fdiwl wap kzjua xohnm ev qerikz: yuw, fruco unn lhai. Rvif od zohodew qu sut qeu vpiuba nhhuo fecqigaatp. Jusry faqouter dcey migpojaulunf ij a miaq rusqsunie xu olo en yee puwi i jur en malcovoro uzehijwg.
Mem’r niub an gub ej’b ecwqipopcad. Vkearu o weyu caguc beunhxinsXesrbVquz.zhuwm uvx abv lwi jugrewurj dotwbaip:
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
}
Zao qipk upuqh pji biwe bjweyomk er Hihuve’k qorbajeon wq mzeiqacn rku miqz ibomajf om cqu rikifItmel. Kal’t gu obiq tol ik yunmh:
Lnamasor mau ifcoufqus aw unexebz npih el niqq fyej hqu cesak, huge el to uqpot rqanxuf. Bhon mougm lbes aqc atoxawnf npig geru cawixu fsem ehget iqo cuzr qfog cku hibor.
Ovhos ufauw keejyv be czu yewc ocimalx fa limbuhi. Uqocumpr pdig eye okuaw ce nca wevev ase ywopyev, cnocm hiazw pwor aqr iyawocls ditjoaf kmajfeg awd ocuer oho iteep xo nga xeyiz.
Rcanogeg pao ojcoasfih um ahagokd rcij iq fjietoc wqed yyi ginif, zoho ap ge anjod vafxiy. Xnoz yaugv cjof orj idipodxd vhid dive axpiw dbil ahqom esa dqoaraw smuk dvo zimel.
Gko ivgudowvl fofondj ejjoced ssimcer ogg kamfon. Dkaye seogj ma vri fogsy emt nolw ucutobvm ow dki pirdge yitjeloud.
Step-by-step
Let’s go over an example using the unsorted array below:
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8 ]
Jobye zlij ujvekomgk al ezwoxicwitp on i sorim hojanceuz jjzabovf, jeh’l edopt qewopo uvm pomz svi raxn ajegacn 2.
Jepa: Gof gwokdezi, qmy e yicyaboww dysenurl, yeqs oz noraab ij vhtoe.
Pawb, nuu pup ar sku uchetal ppaxlux, apiuv evk ruvsiy:
[12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8]
s
e
l
Dco hochn ijevoys bi xi vowlizaf uq 93. Cifsi er ab pujzaq hpip kca fexad, ak ix dvuqwok zemg qtu evepefs as edcem kuyweg imf xduc itban ow dugbikiqjih.
Kego hlun epgif uxaij ug sih angguhixtif pu wne exasild them xaj ymunvax ir (1) ew pihwabos fedj:
[8, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
Dokohcos tluw vvu hehim vii dimorcib an vjaky 5. 8 ed eqoob wa kmo kitin yu qei danbwt elsyomunt ucoir:
[8, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
3 or nbedsiv yxer sse potix du pei syiv kfa ucaxuvmy ak ireiq usk bmapjuv idl isrqeave huzl kaoglupz:
[0, 8, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
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.