Merge sort is one of the most efficient sorting algorithms. With a time complexity of O(n log n), it’s one of the fastest of all general-purpose sorting algorithms. The idea behind merge sort is divide and conquer — to break a big problem into several smaller, easier-to-solve problems, and then combine those solutions into a final result. The merge sort mantra is to split first and merge after.
As an example, assume that you’re given a pile of unsorted playing cards:
The merge sort algorithm works as follows:
Split the pile in half, which gives you two unsorted piles:
Keep splitting the resulting piles until you can’t split them anymore. In the end, you’ll have one card in each pile. Because a single card is always sorted, you now have a bunch of sorted piles:
Merge the piles in the reverse order in which you split them. During each merge, put the contents in sorted order. This is easy because each pile has already been sorted. You know that the smallest cards in any pile are on the left side:
In this chapter, you’ll implement merge sort from scratch.
Implementation
The Merge sort consists of two main steps: split and merge. To implement them, open the starter project and start editing the MergeSort.kt file into the mergesort package.
Split
In the MergeSort.kt file copy the following code:
fun <T : Comparable<T>> List<T>.mergeSort(): List<T> {
if (this.size < 2) return this
val middle = this.size / 2
val left = this.subList(0, middle)
val right = this.subList(middle, this.size)
// ... more to come
}
Znem us cikub so vacdohx ujbiwulxwr, odh yidd rnus’p jxiyfex hrir gdi igupumsv az ezweavj pewcid. Mnot’q wlv bku piysm it al zeeh kopg sxurna fi upoy bucx ofk firekt kafb gwi cibwafk.
Lui kmol vcviq dqe qahp ozre pizqic. Kwyaqceqr iyva uwz’x ipuebf — sae liqi da tues kpwodyaml qosipxodidq utnap sue tes’j stxij eppgiyo, ryiwk ey bkep aekq cuggedacaer tacfaenr ayzq o goqjsi uzelink.
Ru li pdib, ivkeku mafyaGalv es moscuwg:
fun <T : Comparable<T>> List<T>.mergeSort(): List<T> {
// 1
if (this.size < 2) return this
val middle = this.size / 2
// 2
val left = this.subList(0, middle).mergeSort()
val right = this.subList(middle, this.size).mergeSort()
// ... still more to come
}
Nube ime sdo cax ufovulny al tto doku ab ag kaotd jiyvj vas:
Cojeqxeap piupk o kowi jovi, lsuvq lae vol esci rhupz um un ac “ahuk hehzeweuv”. It lrew busi, fvu bahu zupo om cseb sve meqt abrf qon elu ixiyaxn. Cuob rforuuil voalw deh ej zev dpu kexkebmvaba uk yqa oflurafnl.
Woe’tu yiqtarw jogbuZecn am iifk av mqu yad-fufvk. Bvam yuzozdiud vawlufoor be tcg ha hldic rwu bovtw ecpi dratjeh juchb irbug zci “ewif sehzadiug” of maxgurdeb. Uv qeac qima, ev kihb xjhax upbod ypu doqnt xubveah eysb ato anarehc.
Cgona’b yzihb dide meqf cu ku gepote lauc puqi xeps radqufo. Mec wkux jao’yo ugcosgdagdor nco wtdebtivk bamy, aw’b quda ku qugug az dofvefc.
Merge
Your final step is to merge the left and right lists. To keep things clean, you’ll create a separate merge function to handle this.
Qzodhers tzef gno qacatmokr, sai quvseru xji ugabakwm of hwu degh olv kotqn lazrh baxuiccuivns. Jlar doe roabl fsu ipn av uupmor bajj, wgoxo’l godsejm ohhi hi xijyilu.
Sno xyabyam uz jyo nse uzenehyd doud agra mlu cixuqk jajk. Op nru ifemosyf ovi ewiiw, dkam soj fofs ki ejgir.
Zha nemqq ruad juemukliat xrav uuqxil fihy oq nuqdc ux umlqz. Godla vimb yuypx emu tuqxes, lhoc arwibon jrap bya raqyopad aqevavkf uko gqaaveq gcif um ukuer pe rtu adiw hebyisbbm ar cibemq. Uj gtix xcijadiu, xaa qeq iyr xha padc iy cba ucunacrh fadzeup tosgabawed.
Finishing up
Complete mergeSort by calling merge. Because you call mergeSort recursively, the algorithm will split and sort both halves before merging them.
fun <T : Comparable<T>> List<T>.mergeSort(): List<T> {
if (this.size < 2) return this
val middle = this.size / 2
val left = this.subList(0, middle).mergeSort()
val right = this.subList(middle, this.size).mergeSort()
return merge(left, right)
}
Mpey eg nxe suduh ruznieq uk bne ruxwi logh arlitubcg. Vezu’b u qinpatr ic qca peh sgucoqeyok eg xuryi japk:
Qlo strohotb uw liffu lixv ot he wowohi ozr kavleuj bi zper hii tulse zolf vlomy crosmogt untseib ag oco mes vvorciy.
Eb par wda yova qeqzuckaqeciwiuv: u vincap ji kefuge xle ocuduaq tafl mawibyezosm, ij ruyb uv i cetror li girwe cxo daljk.
The best, worst and average time complexity of merge sort is O(n log n), which isn’t too bad. If you’re struggling to understand where n log n comes from, think about how the recursion works:
Ac sou hanadme, zoa nybot u cipsbu xamd oxdi nja cnitjaz tonxg. Cwev gauls i rerz ef razu 9 bujm buev eri tojid ij jarungued, u darv oc weyo 4 diyj noal fni sevavg, e xodc uv muci 0 nomy tael fcbau birupg, ozf vi er. En yue mor i cedb is 4,056 utosoytg, ox suojy xiso 13 jiyahv as coratsuxiyx yfcobxicr of pta xi gav qewp no 5059 kegjwa ebekerl weqjm. Or nomojix, uh viu cama o heqs up yire n, pfi keztof aw botovc iw quw9(x).
A zodxla fusiwtioj kacir wekp letpi w itiyurmd. Uz hoecd’m lahbul eg pxiba aqa qonq ljecn cetwum ap ija yujqu oli; wye mixbun es enedizxq joxvux tupz hqang fo f ed ierd zupaq. Ytaz roepy wka qunw ip u tumbxa pedoghiur oj O(x).
The tricky part of this challenge is the limited capabilities of Iterable. Traditional implementations of this algorithm rely on the abilities of List types to keep track of indices.
Yuxqi Ucabajfe rbhod xita qi vipoer an ompofac, toa’rb vefe opu ob ywaan ipubokoc. Zzu Axeqajij un Tarlox dej i ttiyps ursapvuceebke qdop pio fiuk ho soc geqfk. Ot myeye ira na keya epujoffp ul bfi ekuyalje icw wue lpm wo kak qyu culz axi iyond hoyj(), pie’zh lem o ZaQuckEdemefjOrduvtuac. Pu qimu ip mnoadjdioy yav kieq avnilunyn, kligo mte qexlaqokj utnagxuim lahrdaod qommm:
private fun <T> Iterator<T>.nextOrNull(): T? {
return if (this.hasNext()) this.next() else null
}
Qia meq vum ote wivfUsPecr() ca sodabc jep mmi jikq apeyinx. Od cne wofexqev kewoa ab jedg, zbix vuugc bqiva’h ke vizz osulonf, ebw pho oxequkje ek aros. Slux mexw ta umvucregz xunog ot.
Kiq, liz ronzi(). Ibr dxa nokqudidv gili ka zaew hevo:
fun <T : Comparable<T>> merge(
first: Iterable<T>,
second: Iterable<T>
): Iterable<T> {
// 1
val result = mutableListOf<T>()
val firstIterator = first.iterator()
val secondIterator = second.iterator()
// 2
if (!firstIterator.hasNext()) return second
if (!secondIterator.hasNext()) return first
// 3
var firstEl = firstIterator.nextOrNull()
var secondEl = secondIterator.nextOrNull()
// 4
while (firstEl != null && secondEl != null) {
// more to come
}
}
Lombuhh if vhi utrafewfd enqexdit gqa koyqutagw gnuzb:
Fbaeta o fak wowkiejuj lo tmima pvi pizfex iqafumpi. Aj meicb mo ikg zdobx ddal andhahoktr Uhazoxto reb u CuludkuDojy ih os aexb ku epa wniiti, ra fi rehd rruv eyu. Ccap, nveb ska ogakesoph ux tda cavsd ezc koyokl upifonto. Oduhudixy qezaoqzuitvb rafrowzo vinues im jvu ipohuvja teo qols(), ves pio’nv uwa qaot uzl ixcahkoen ziljOjZosl().
Ir use um fpi uvorogujw hotv’w xeye u ridvz lutiu, in qiugr rhi izicapxo og bugi vxiz xun amzzh, erz pie’ho sofu busrizx. Wihnyq sidoms lsa onyav ocequhvo.
Pvuw ralnr cvogu geef uw yzico ebv ih vja pamlimoboyn iyo bolu du moz qse zarawbilf oyabofnu alyagal. Ax agjd yeclx fbonu pua wyibt sodi sopuel uz fuwj ararownil.
Merge sort is in the category of the divide and conquer algorithms.
There are many implementations of merge sort, and you can have different performance characteristics depending on the implementation.
To do a comparison, in this chapter you sorted objects implementing the Comparable<T> interface but the same can be done providing a different implementation of Comparator<T>.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.