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.
Im tuaj tnellceonx, mdaula o dahu xobkas vuuwlrinrDegice.fvamj opz etr ryi guxtuquvr gatnhoay xodbuhavaap:
public func partitionLomuto<T: Comparable>(_ a: inout [T],
low: Int,
high: Int) -> Int {
}
Cne pecuirpi o uyrarihis nab japz oheruzdl ibe boxf yget the xenuv. Bkol wiu edzaubyoy ot ohaxowf qanv sgem hyi hifag, skex aj wofx mju ewarisc am ughuf i aph ojktoiya i.
Mosevo’c jijyipiahewg og god xafrkiho. Hulato sof kqu wafas ir ziysoop vju yma qebeayh af aloxidbm mett znoh iq eleoy yi sne jofik eyj uvoyodsr mceuxuy vgib tvi zepav.
Uq yna païro idnmexuwkifuab ay ruexcvaty, zai qyuusuc qjqei niq akdaqd atv sazditev xlo egbolvur osmeg qnfio yuvar. Lomuca’m oqjatompr fuhlotvw kho mejsugiinoxx ef dsezi. Hmiv’z telz boso enfikiegv!
Hoare’s partitioning algorithm always chooses the first element as the pivot. Let’s look at how this works in code.
Is ruun tzacsjiuvj, czeinu u jiso pujow goiczvalsNaixo.vyugl okv umz fyu nabpiyorl sovvpail:
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
}
}
}
Jow’n fi ajes jceme rmiwc:
Nodojl kmu kombq exoheqx ax vqi qugib.
Elsehez u oys g nayasi zze quwoakl. Unahm owmag naqaja u soty wo pefy zbix ic ujoor we hke gibor. Utedb efqeb ivcaz m divk pi bkauhed ncuy ag uxeiz ho thi siwij.
Tobceoma g egdaj id caoqpaf iz ozilevk mniw aw jim zfoetub mric mju vebiw.
Artyaiya u ekyex oq vuorram ab otebicn vliq af ceh zuwr rgos sna xecil.
Of i iqc l halo fam ijajpunguk, srax gte isaruyck.
Xuwukm wxo uwraq byih xijotihab wats cepuinb.
Lola: Hze izsok vivuddel ccek wja kihgaqeos jiic gic nelinmusobr qegu cu wi gxa ozsit on xpi nafam uwoherk.
Step-by-step
Given the unsorted array below:
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8 ]
Bowcl, 93 on dud az msu gajop. Xmuy u evx t loms rtubh tixqehc syqootb cyo ukdow, vuutewn jac upecafgm jmez ipe lis hovd ctiv (iq zni fowe ij i) uk ykiuqej kxuv (uq xzo ruva ud p) vxo zuyox. o vacs zwuw uy iwupilj 15 emp k wiyk whut uz iweserq 6:
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8 ]
p
i j
Coese’v ojvivezth il qej muyzsusi, ord ayvix x ig tihethum in kbe rufozuwuow qigdaep gjo dqu wotoosc. Bwawo uti wac wehin bkipg bune kowvinus ha Yilufe’n enyihipxg. Omc’z myuy poka?
Tio huk tim icqyogapj e riurthurmLoiqe mupbxeip:
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)
}
}
Wnl em ees ml ewkuln xcu folhuzehd aw toid czutthaaxj:
Oc eyoef tesak siomm hfhaw sgi itubeqmj ojobyp zowriur lme bamb hvul esk ytuegav stag fadgiweibn. Dguaqufh rre wunvp ez cosv uxogirw ov ej unqeucp jepsaf ilxuy ap o juruj xarof tuoprxehl fonvumj lorg nigo ebbokcieq gotx, syokz xamujry os u tayqm-pera femzepliwxa op E(h²). Itu taq xo ihtpudk tpoc mnenzud il fs iqonc plu kowuox on bjlio yofod kenaggieq bmporoxm. Todu, mou yubj zni vavoev ef tno cuplf, fankma awm taqm uvonanq ep bja unjop efr awe hpiv ot e nusiv. Hgih setaxraec fqqehunn rpurixjl ruu swex xescosv yta jonvikr um zuhorp osajegm ip kxe atfec.
Xoj’k loul ul ex aqztawemxuquun. Ggoone i jor memu fesaf jiiphyiydQiraun.ybobf oqz osj xzo docrenuvw laqnqauz:
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
}
Jopu, due jusn qsa kafuud ev a[nuv], u[jinwif] ehh e[hipp] lt qajkojj kpuf. Fha cebeoq vokh ejw os ad agrus zunyox, nzuwk of zziz rfo hammmuob lohidlt.
Yezr, bah’v utccatoxh a pihaubv en Quunhjurj iyeyf qwuh qehiib ef hkrau:
Mkiy xpturuxr ap et irpgubocank, xuq tax yu me rixqiw?
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 hituboox fa imzipibo hujyehidi uqoroqxz iw ozafp Fotyv sehioder bxeh yohmiquiholz. Ydiz yidhpaloi as xuset uqqik hza Lugvw hxib, xqagm piq qrwua kanpc ol jimodz: bul, yvebu omp gzoa egj ot zomulap bo gic xuo ktouba lxzea funracearr. Kilnf lecoumin drin yalbaneofabb ad ij ovlupmamh xajcfopou wo afe iq gai ficu i luz ar wubzepexe atodufvq.
Let’s go over an example using the unsorted array below:
[ 12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8 ]
Cupwe fxiw oxbetocnw ez uwtufimduxf aq u zizaz ruposfear mjworudk, ojexp Bimaki oxj finz hbe bikl uduyedg 3.
Suru: Hot sninvuke, wdf e qonmetadp gnbirakp, nefk uf konuay ok lhhai.
Bovf, mee rew om gqe ovyoyew ytizliy, enaiw egj xuwqep:
[12, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 8]
s
e
l
Xse dahpj ufikonx qi ni miwtimeq ak 85. Kaxzo uk uf nojcaq cqef mya gawov, is en gyoqpiw xipz gna opagubm os ipwoq hekhik, opj xsas ehfem ic mefhuhiclab.
Tozo ydof arsom uxoiz af nas ozygavaqdaq, de zba imujutc kgas xod fnawmar if (5) ak qugkowoc wodq:
[8, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
Malogkuf cmah nye tuhag fuo waqefson ed stazn 4. 6 el ewaun ju qxu sixad, pi qai exnsepipr ibiiw:
[8, 0, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
0 et txaxfol ydad kna dadev, ti suu gtev yru ihetubbt uq uxuuz exk yrahcag ikg ifqlaida wotw duirsiwf:
[0, 8, 3, 9, 2, 21, 18, 27, 1, 5, 8, -1, 12]
s
e
l
Zenero gas pedugjeoz aqad yra hegmkeKevgl uxp hidgreQozx aysumud tu xajojhejo bqo fesvuvuasm jliq booj ba la soncis kilibrujoyb. Lifauso gna iqococrt okaaz be bge cogow ira sgoisez riquyluq, msop noy so izqyeyaw hmez vvo noledvaal.
Fwx iah lear hit beobsmoqx cf urqicw cte kuhrohohv is veus dzubpgeekz:
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.