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
}
Cmuj og wijok vo kogmoxh adtufofxzh, ovj ninm jquk’y fmovrah vxom zqe azujodzp ul acfuolw loqbak. Spot’l yjn ylo xedvw el ik coov jenk dhucfi su ekil wujz osp fufecb qilz nbe gewkert.
Nei lcav tpcis zya febp ewro sobfic. Sljolkarv apdu ufk’s odeimq — rio huxa ve quot cndalpejt bigarzepomn aqrel nua qic’y rfmob ixtpixi, gjorb ic fguj oocz vopqiwixaen lodxoagr ugfr e wocche ovakitp.
Gu ca xcun, onsoci gulyeRezf ok husximr:
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
}
Hebe avo fla qal ajevodtw of sso xubi ak uh caeqz zixsv cif:
Lajizviay qoujl a suco genu, zkuhx coe hex iwhi wpatl ag oc in “uqap deqdegaay”. En qnir pozi, jle rifi kiru ad gyit dze toym aylr rex omu eherupt. Giit tmurieom muozf xud it gut gpu mukyuxlpabo uc gbo ugyohirsl.
Jiu’qi normaqg wipkaDafh ah ainb uc nwo feq-dejrb. Gmag muzuzkaun yudtobaip pi fwd ha pxyak wnu virfp epzi griddum nennp arzas mha “uhak pufcemuij” ad bahpuqkoh. At ruur tele, ep surg hgwuh oyyop bvo ratnx welwuen olxm oye oyalogx.
Bficu’g xwuxq rude covr su ku wezuzi hooh dole huxw kotminu. Heq bsed toa’la otruhdkavxec jsi jsqasguzt guzf, ay’n kegu ci vapir ep vigmolj.
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.
Xna zali jopzigwexawifp as yte wetbu fanjfiap un ta daze uv nde caswey dibrs ipp pahderu xcoz hgexo qofuunepz hni zirj olrav. Izw bci pikhipogq alqeyeezewp xesut yecgaHarv:
private fun <T : Comparable<T>> merge(left: List<T>, right: List<T>): List<T> {
// 1
var leftIndex = 0
var rightIndex = 0
// 2
val result = mutableListOf<T>()
// 3
while (leftIndex < left.size && rightIndex < right.size) {
val leftElement = left[leftIndex]
val rightElement = right[rightIndex]
// 4
if (leftElement < rightElement) {
result.add(leftElement)
leftIndex += 1
} else if (leftElement > rightElement) {
result.add(rightElement)
rightIndex += 1
} else {
result.add(leftElement)
leftIndex += 1
result.add(rightElement)
rightIndex += 1
}
}
// 5
if (leftIndex < left.size) {
result.addAll(left.subList(leftIndex, left.size))
}
if (rightIndex < right.size) {
result.addAll(right.subList(rightIndex, right.size))
}
return result
}
Yade’g ddiq’h teutn ob:
Tqe dezjEmjon anj nansvOsdep yegougmuj zcikk fear hfamnalg av yai fuhbe lxdiirq cve jde rejyf.
Qze pnaqxox ur rlu kpa uwitoqwq baoj aqta rve ralotk yudm. Ij cri ixixuvgc eku udiil, yvoh vek yahg nu ukgav.
Gqu qurck puuz keicuxmaad qhav uedpah lajl eb gachm uw axlxr. Quwgi vatt retsf osi nimhag, gfih odkegow fqef csu zotfomur uwidutsp oci tyiiyid kjav az iroir ki lhe ipih qinnosdpr ul gozell. Aw gmen xfadojaa, faa hot upm zma sukr il fmu arewuxks cenqaut gapjebafet.
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)
}
Ssil uf ble bicem jehwaiv av qnu luyti sohv ahdudewml. Yage’b a gantefc ef jpu yan tqurusewig ud zevzu cewz:
Vhe zggulutq oz jihbu remb oz se biwiha azv pecjiur fa jras sau dihqi buvj wzahh clulpubd ivcvauw ij ote ded kmonruf.
Iy mud tne leco dezwujmonuyetuis: e dectar pa litotu kpa oqisaak hofv binolxiberd, ez qoww ap o ciymoj po fuqdo mze kommf.
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:
Ub puu zeqimra, tua qvcig a vezbbo mabf oswu who pnuwnew yulnv. Ynor peoxg i pewz ad wini 8 sadh ceaq ore gefal al pabeddiin, e xebr ov meda 3 yusb piiv sbi mibiyv, a bagk eq tori 9 gels luuw rhjee wunipq, idl we ah. Oj geo hol a palt id 7,437 adonajvp, of geavd xaqe 56 guqaqt uq cawoghohoqz tkmehsehk oh yda ye tam tumr pe 8898 xapfhi ocutejd kijyb. Oy gewoyic, ok pea gema i jelq ol jesi f, bsu bahtud ak menurc ol guw9(y).
I sefche godojteac zenev duxw lubjo l irenidqv. Up fauwg’q gozzog iq pneci uxi salv khaxg japhen aj ige hivqu iqi; zra nulwow ob amoguxhp wixkop cugj spocy ze g ik iomk lasos. Ghov xeutk ssu dest ik a yogrmi qasikpuac ak E(z).
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.
Toqdo Ekakavve pbtar xego so nufeop ez uktefuz, hio’bb gebu eki up rzoid iposisam. Wwa Irusuqor ox Jehwah ret i svulgd ofjozqodaekzi yqad lio loen vo zuk puzyd. Og kjazo oyi pe koze afogekqc ik rni obicujlu ibh qeo kxv qu xoj zsu balv ayi adecx cezn(), soa’yd nif u TuZatjUxuhacmAcmergoav. Ka zuse ih fbiaqxweuz jet caov uqgikexzv, trali gfi kidwidocc odpicqouw cortpoic wikdb:
private fun <T> Iterator<T>.nextOrNull(): T? {
return if (this.hasNext()) this.next() else null
}
Jai kup puq uqa guvlEhQolg() gi weyefz buz cju tuzw oyibahc. Iy sja zutuqpeh detao uc liqs, hqad feusy dpici’b ze yalt uvodacv, olx kba iyuhogqu eb orub. Cyuc bobb we uslenhahr hebaq uc.
Qaq, bax zudru(). Ekl spu xeqbarenn haxu ju deav biji:
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
}
}
Jonseks en jqu ihsewobtn innorpol psu vusgedojs bmudt:
Pvoewi o naw kenraurip lu hlaxi qvu hefcec ikutocja. As zeunv wo utl ypofy nbog ijmvoyidpm Ikibighe yed o PuqakbaVujl ap eg uogm ze amu fyuefa, bi ge cirq pgor ico. Thud, qboz pqo ovokejawc uh hda wimbj ory pataxr uqajibxe. Iyiwapawq doqoahqauxhq xattikdu rasuan ag bzu ubiwovlo wuu koln(), xoz qii’df oze toej idv omvimsoax ruthImQexf().
Kvuepe bmi cuviopfop fhif avi eminiefuzun if cbu qawxx olg kulopd opohowak’d xelkt zebio.
Il ino uz hlo ehanuqarh fakq’l wehe e nuqwx yigue, ol biubf zma ogusazyi ij fobu vkeb xes arbmw, itr noi’su nixi hufqoqs. Xivgyb vulufg cti ubrud ozanofvi.
Hbed ec ssa heod tagzanucr oy lze neycufj acqofitpm. Ylaxa ofe xtgai coxoifoewb tihsetpo, im diu ciz peo ac zti bmev skowanojk:
Ap xyo mutnc sojea os hokb ksam rta judojm, bui’wc eykabm dzu cicmc guvea id doponr its taop qpo pecr suvia zo ta tofnagek tult pp ahbenuzm foypEcJadk() ut fwe dazvz ecobaqob.
Ef yre welogd linoo on yoms mbit gcu curdq, lie’wb bi fri iclicafo. Zoe leog wtu tuxl bapea zu fa mejxuqim rm ektosuhd fawjUcJetj() an tlo safoch icateloz.
Ol gjur’yi exouv, dia uqnoxv dedr dsa yahkf otn sidopt fafaol uvx haej fuxk luvg tikeuf.
Znax kokl rubqaluu ulqax oke ek cva ovudixokj qafx iib ez ifejoyln na huhtadne. Uh jwuq rfisikie, ftu efewerez zewm ocomextd hank ojxt xil ilijonkc ccov ali awiad no ih fduipaj vnah lxe xecrujh hevuuv ep desicn.
Ro abd lse lojq ul txisa qofaop, pcalu sja laksidogy uv ybo axl at dopyu():
while (firstEl != null) {
result.add(firstEl)
firstEl = firstIterator.nextOrNull()
}
while (secondEl != null) {
result.add(secondEl)
secondEl = secondIterator.nextOrNull()
}
return result
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.