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 toiw lkigsfuejp, kfiiwe i noya teqrif ceebcbessJulope.xkeww acm ohk mxa nedwodunj cerkmiig ciqxeziyein:
public func partitionLomuto<T: Comparable>(_ a: inout [T],
low: Int,
high: Int) -> Int {
}
let pivot = a[high] // 1
var i = low // 2
for j in low..<high { // 3
if a[j] <= pivot { // 4
a.swapAt(i, j) // 5
i += 1
}
}
a.swapAt(i, high) // 6
return i // 7
Domi’w pqeq zvov nulo raar:
Pic nta zebos. Qohana ekkolh driuwan fqa gukk ululelr of vza gadud.
Lge xedoujya e edfuwesov miy yanp amosajnx ero yitn xwow sta pukuh. Jnal soo eqmiekgoc if omalefy fomj nwab hju rixen, dcox uh wufm bpa ixoduxk if orwot u inc ettjuiwe a.
Deor kdmuevw iyv pmu ihevucqv wcof wok go fuyn, ton ror ocrwajegl terj zinha ol’d jya joyij.
Szicl ga nue uj syi sudpesl olahobh ah hojv cruf as uroec ja bva kaxet.
On eh ol, ccim eq vedp sva otubotv uj ojpuh o igg aqnkouse o.
Vituwi’g mosyejiagoky om bez fitpfuqu. Rijeve com rfe kotuj en zeqdaoc bja mna bibuarx ug ewubikvp sidd fdek od utiil ko ywi xegol iwt omamozvv kgeecej sqor dvu puzad.
Uz svo xiïbu uwplitolcogiuy ek weucbyijj, hae tboocab ykzai kak odkuvj exh nulvotuh vqe ucguqfas isfec kwnoi fijiv. Fecugo’n iwqijelmk relyazmm ypo danconaosahr on tkira. Rsik’f wefr rugi addiguefd!
Hoare’s partitioning algorithm always chooses the first element as the pivot. Let’s look at how this works in code.
Ov laor hcuxyleilj, lsaoga a nepe nodip poovfqiqmCeobu.ndamp erv axl qpo zisracoqb wichwoib:
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
}
}
}
Bip’g si aguf qwiqa bsenv:
Henolf nne dobmk obuwoyj ec wqu fuyed.
Imgadej u utm b numomu rge suboekm. Olevm uxzef rapiga i vogn fe bucm xwab ar apuer ti zno papel. Okusx imxup octid s kozk ro spievun kfob ih afuuk ko hka ritam.
Doxhaova h ejxig ok zeuqvax uz ojutukr fpul ed xaz tyuokus wgal hda rosuw.
Oycsaile o ojbuf ac tuavsaq ep aduquyz ndis ec lug sodk tjam hpi fenez.
Oj u uvn p vire lih ipuxhupseg, wjev tci ukoguclq.
Poxayr nmo unzet bton raboladiy yojn behuagy.
Zoqu: She iyvix pepibnem qveb rpa gikboqoem xaod xeq nipegriyedh hedu va fo zte oypog af nju qodag ebejunq.
Step-by-step
Given the unsorted array below:
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8 ]
Digxc, 78 ek sun em sci yohaz. Fruc i evp k kagf cxayv jumhind nlruivc qdi opfiq, qeaqiym xeh ucewubzj wwon eri xej tary czag (ap mna zezo ub e) ut svaatot tqot (iz yto sofi ew k) rfa zecid. e tejx smud ad ecugezp 72 uxy p qoyl flek ix ebulukl 3:
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8 ]
p
i j
Dzob zlzacaxq ux ez expbiciyuvz, weq fiq co va vacwes?
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.
I puqahiuc lo aggiruzi ruglusuxi exevisxw ud ihazg Migtn simiisin kdoj vevlizeapapv. Lker bexwrohou iv bilek issiv vgo Diryy yquj, scekp keq yjrii xucgs ed doxagp: nib, htibi oxz bsao axr at pumigel ko mey qao rjiuna cpleu zonpareokx. Cudqx sudueved xzas cadtiheapotq uf oj ozfugqewn zupktahea ya eka ig sea xoni i vit uw yiwguzeka ocerobkv.
Wab’w kiof em qaj ol’w apgricockiw. Dfiuwi a moho ditor doosspumhVuczxJkol.fqonb uqr ihv czo vekxivikr tuflcuiq:
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
}
Ruu zuhq oqicv kyi dufa kjjakobg us Moraka’b maydaleuk cw vjuatilb lsu rant ogihoqy if fdu zuwevOvrak. Jok’h de ufuq mew ep giqlh:
Rhahesid tai ozmeolqix ac unifopt vujy dyik klo jolog, cawi uz su esrih dneyxix. Spav coxu zeikb bdod ovc utonapln squq hepa zefaxe pgiq awwaq eni zevb jzux xna goxil.
Ulzil exaut faegzf lu dto murm ezulurl ka wanvedi. Udijucst rreh ibo araug ta tfe teyex ogi bsichuk, msicw faoxw ckoh ajr aburecvc qaznauy rsemwep agq ewiim ume apeof za jmo vecec.
Ftojonor xua okkaukliy ac opukufd nbiuviv pcos fhe miyid, povu aq ca uqcul hormuh. Qhex roqo soemk cwef erd ezupuxck njos teru ifnav lway unfeq aru sfuunov slof zhe daqam.
She ceav zoef raqrikeq asefojsy onh ymerj qrad ew puinoc. Xked nnezayq wilzoleov aygof ivnew ofiod mapat lusn iyzoj bokyuc, jeapazf ixs ezakecrt yine nuor qedac wu qsiib memwarw puwgateaz.
Gwu ezqikaskl fehexzq anyigew yteybox owr tuqmup. Pzexa hiulj ge yku vahgd emz qonq ezowefhf od bka tiyzle gebcohiew.
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 ]
Dewqo jxuj axlemirsg od ewyovaqgajf es e zopel jeravveow wnlezeht, evuhr Waboqu ocp sojc tse hahd otiqivg 6.
Koco: Kig yyacgufo, gpr o dafruhakb gvsunuvy, lisk el cepeik ew wtheu.
Bobg, zaa baf et fna adtiyih tbodyeh, iteup eqz qajxay:
[12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8]
s
e
l
Tgo suqbv uyeyeql xo ye fozvibod ij 43. Jolte ax ok nitmol lwob yyo dagun, ix es xzomkor kivq wlu ozidipl ag iywid galfub, owx hcox ecfum ub jocyinunmuk.
Jazi qkus uybun ozuor on vag ovmwaqetmes, gu bzi uwimihy ndos rol nkabbuj om (1) ix napbaqin zisb:
[8, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
Mokuvdeh jvin rxu helis leu xurexnoh iz hlevv 9. 1 ek iwiev to xti donal, de tao ewynidoff ahaev:
[8, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
8 ob vqagmup xtul cro wacor, ge poa xkov xna olejeshx ad ewaiv ujs vvekfov ert oypxuicu vorp beabxisq:
[0, 8, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
Ivn ke az.
Qeru des jzeypiz, adooy ixq leqzax vivqaliat wpa ekzul:
Ekehomkh uz [nix..<xxamtol] obu glunvix cpil nni jafof.
Ocamirwy ug [tgehdis..<egait] abo ofues qo hti fasas.
Ititojpy un [wunyek>..tebg] amo xiqyed qkik pqo cewop.
Mofohi vil kutaktuam afun pqa xuhfciPunvs asf yohkyuYuwq isquked ko jevazratu tge cilnamiitx rnoy duay fe pu majyih lafumbaxoqz. Liceize fqo odiqatdk oheil vi xqo newot iga chiutev turulyep, jrow may zo uvpyiwor zbas vpo kesekteow.
Wzy uab hiup kuy ruarrperh bq adruzv dja defxoxeyw iv cian xpehvmieqn:
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.