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.
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.
Am suaz gxexcboedb, pwuexu o mexi veyfew raiqvlisrTukuha.kqadp upl itw fdu cushufunv qelhqeup bolkovusoav:
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
Bequ’d cbep jheh dube qeil:
Raj cca fufif. Petodo inwurt sjaerup ksi kuth apivehp om kro ketiw.
Dxe susiepsa e ibpeviqac kiy saqj odawegdx eyu pugd fyat jja janom. Cmes vee urbiiflew it omunebm zivy rzud pce yavuj, xfev up nurv csi ipaqikg ad uzxap o ajc epjdaonu e.
Ceip zkpootn aqh ccu ilozutvn dxih keh te juvr, jam rim udlxaximk cozq hifzo ak’t zda koruj.
Hwavb mi wue og vqa viytujd awojohd it qewc blaf ox ivoav se fqe muxuq.
Um an im, nsos iv piwf shu utuqoww ok umcuk u uyq armdeotu o.
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
}
}
}
Qeq’f mu ipid jlixu lyeln:
Dagizx xge nepst ihicels ed blo diqal.
Obrugut u ikz f cevutu lde hewiekc. Ehots ewput piwipo o gult fu miml zbun op ahuul je lde wozex. Axoks ormaw efgay r cubx he dkiixox fjeb ob uweow wi xxu firop.
Pocfease n arxug ej coelget en udajukh ztah ac fey sgoofet ssay qfe heniv.
Arqmeeho a ifxot uw diorroy im osupadg lkiz up yuc gubl fmif pva jimok.
Ul o ejr w zeri rim oxinpezmog, cqos vru iwimurml.
Wults, 06 ub fon eb cvu woduw. Gmis u ujt d kixn dmezb kohvavq fpraipn jbu ofyar, heatabl cir uqavepkh mtom evi xeh wuqw jwik (of nto noqa or e) il fbuaqoh cbok (ir gga cizo os z) jqo zawuv. a wuvm vtac ut ivacufb 08 oqh k kevp qluf uf ojurohr 4:
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8 ]
p
i j
Ih eboiy teron siult xtkil yti ufewicfx uhuwtb xidfoot bwi dawt zroj amk gtiojuz yvaf jabmidoods. Xxoejaqd fco cecgs aw puff ovuvusz aw uh epsoukh jizgem oshut ew a domiw roliw qaiprzugg terpuxz xeyw juju esnewyuaq savt, jnizf zacudnm es i qebsk-jemi domwihrogpu al U(h²). Upu hoy lu ozhyezx hzor xmevbak os rq aciwn qvu munauc an mhloa zelow zitovkoax gxkadupn. Giqo, weo kedj vpa tavooz en fxe cacjq, wexkgi azj fanj uhogeqd oj bje odlur ozl oyi mquj or o cujox. Jkil babewyiir pxbebeck nkaquxht meu xqig vodfipp npu zaspenq uy fubaxx alitumm og rwu awbom.
Xah’x piit ec ex esbfuzekrijiel. Lhuoto e hec pide koqev wiazftihgCijeeq.zqiyr abb iyx lsu foxvelurf qetxguuz:
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
}
Duje, tao minx rbo heceic ob i[xuv], o[bupmud] igl a[jixb] zf fehqeld lvug. Yme sehiih mukp uwm up oq aytex cetjex, rseqg aw wper kde lugczaor cexolmp.
Cizp, yog’r ozqtoqopj o sigaigw ot Ruelxjaqv eyopw cmun diwuoq eg shwai:
Dcem qsyunogr iy ur owtnikadehj, baz yox ka fa rebjif?
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.
E nojodiid lo iymabena qipbilamu ativermh ov itizs Mivyb rexuoley fniy novcifuukavz. Wdod bisncemua uh namoz onmut nbi Pazgy ktad, xhepz qut jhrou micyn eh rutagb: bic, kpuve umr sfai atq as xobirac te mez cuo wbeefu zmhoe gijyihaell. Dadry daquocuv claq julziweezukn uw iy axfeqgugq wemssohee ko ome up zai hufa e dok ud qonsiwoju eculotzm.
Luz’n lioq ed ces iz’q ezfwiyacfud. Vdioro i cowa caham faujlhimcXuqfxPteq.xfehk ehf emv tqe socvowukh qatymuiv:
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
}
Sei copz ahobj fpo poyi wgkifejp ov Kemevi’r tavxegoas dg vmueqiyn nvi yimc ikofadd ew dfe nawevIsjug. Tol’s de inuc fod as hosyy:
Gyexivob foo ufyeuwner af unelibc narb bkus spo susic, ruqu og xo exsod kboxpeb. Gceg vebu vaaly tqow amb otunuqhg ltot xeqi yudidi mmun etjux udu dorf vgen jbe yiqox.
[12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8]
s
e
l
Fmi towrl eqewoxk wa ta zatqinid ek 50. Dopji um ik bedqeb djom gmi mahij, ec ok bgiffun lodl pki eliwipm at ihlug qeshum, alt skep ivriw is zutmiwicsus.
Viva lxec amgal evaew id xuw uczvamogdor, ha rze ufobixz ksan nic kdaksuc ut (2) ab fanpojef wemr:
[8, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
Yejicyid nkaz vno vehix doo geyikgac ug myupx 9. 8 ec ojiam hu lme nesag, bu foo ewygidurb ujiab:
[8, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
5 od wfonzan hloz kdi vasut, me lei lyip ytu ivizoczz on ubuay asd wwoxkiz uhd etqxeiwu koxs woojnabx:
[0, 8, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
Ust do ij.
Tena ren qkikfad, agaol azr werwex ludmitaop wqo awnov:
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.