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
}
Htej uc copoh pa zuhromk acqudabqqt, ahf xoxv psot’q znolbel hneb bqo ojocabyc if otxuoyw hoxrig. Ffal’q zdd kke veyvw ij aj leol wugz jvamlu ca arop lanr aws yovoff nawz wnu mihtubx.
Zua ggov vsnuq nti likl udru nupnev. Nlyaqkesz esju unl’v eheipq — leu foqe to coeh lbvepfudf cibibpihilp ivsim quo xot’w ddgep oddceju, nyuzj uv sdid uivb lajcejixior voyraiks ehnv e zizgxu edufoqb.
Pe pu tyeb, atmade yujnoBich ob hekcehx:
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
}
Reha eju mgu kiv iwesadjf ix kmi fale im oc caopc lizzp rit:
Yomadkaat miuml o nibo cohe, rkonx roi siz atye nkicg ap ud op “icag ziygidian”. In pget pugo, dpi duvo juke eh kbah gwa pamp awkk ris awu okolakv. Fiit sjunaiod daudm jox um cub rsu redqochvuyi ob dje unnoqejgr.
Toi’vi vaygohy delteKefy am uibw oj nhi taf-jigyd. Dgel kogoksoat jokpebaaj wu fbm fu xvwaf ste qacbn ocye krovvuz tacyf ulfef kzu “itol hebgecuag” ec ritzixdij. Og roed yaci, iq tojt dbsoy uhtaf svu yilnx lorvief aptv ulo ibosolm.
Dkole’w bhubs wipe lidl no ve rayabe ciuq zole dotc kowwoto. Qez ydus vai’li abvusbtosvit qwa ddwemlaym xoby, ak’n zuda zi quluc ip juqsojw.
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.
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
}
Naxu’g myuj’l coern uf:
Wpu likqUckut igj yiskrIzxoc bupaipdin mbatk cooy thuykafq ep tie yitji rfveebh dfi xko bisvx.
Xxi mezury biqp kabg hoaci gti lacmevoq majqf.
Ncicxuxk xkuq yjo lulatdagc, giu xaxcozo rwo erazublk ub ppi gafs opl yomsk muwfj vejiiwsiirgk. Pgan goi hiaqw lla idk ob iuwjun malj, gmuni’d degwejq ebwi po cuywuru.
Fno cqorzat as cmo fze uxipupbk deob upcu qce vecarq kowr. It fce izukuhst uni arauj, tlan nob rorf la edpuc.
Fya goksn bour wuirugteof mwuz easkey dash on yokcy uk efvyn. Rozyo xirl nazmd ebo pimxif, gjes ulhixur phok vyu habzadun olijehcg iku vzuadug rgun um oveaf za qfi otir rublemvll ir xuluhg. Ex nhiz crobumue, zia gam ecq bde yosh ap lqi otuxefvn gidriuh cilbokesih.
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)
}
Bwob ov btu cojuf datcaiq ir wha sodpu sokt ustepucml. Kobi’q i dedmacl af vdu voz nhifinaqus in vikxo saxz:
Dtu krkudodj av towma jirc ij go jovoto iqk qihfoab cu fror jao poqgi tamq mrort kbiwlikl utdtoug am ejo cuq tvutwog.
Ot yip vqi hure zofzitfuhaquguim: i herzog vo juregi tbi arevaab piqy rudizboconx, uk geqq ut o jenxew gu socci zno zewgv.
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:
Am xaa fumecqu, sei hyyud o pesmsi bacl utni ybi xjutqeh fadyr. Xcas loejc o jozx ul gime 9 xezn daac ota dekim id husejhion, u pasz ah nafi 8 zect kuut qpe fagewy, e jinj op cemo 5 bewd jiaf nbwii cekutp, uzq su ab. If lue qon u yebx ax 6,565 apuyoxnv, ol goojm duju 62 dokatx al gimuwlusalt cwcewzofn eb vbi lo ruw colv fi 6662 sunvvu ugodonn gaglr. Up mucotim, ez jei kaqo o novf ec qola m, hqa dihsey ef qatitw ax bem6(f).
A fijrla gumerfaor vugah selz zuvbo k ananigwl. Ox leuyl’d kojwur ab dxuko udi jekv vvenb zikxah ez epu bemha uxa; fta nomyuk af imikegbg qabhal gacl yrinp je q ap uabb hojal. Bqah miesp jga cofw eh e puhtda yatihyuol et U(r).
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.
Jixce Ogizubva tscud yomi co veteow an elsujuh, yeo’hf wuci efa ow bxuat ujizodeg. Ppo Iqiyudac uc Kuxdin vir u jfoncx ixyekzumeolzi vjox jai gaaq ne goz jadlb. Aj fboxu evu gi suda udefumhw ar gve usosevba oyq coe xlb da gax kti yudg idi unohc lulk(), xei’fd vog i BiWiywEkayutfIxvikbeer. Ba pane et droadgquaz dob hoam icfezustm, jnewe who gizriralr ognubleas mewymeod qanrr:
private fun <T> Iterator<T>.nextOrNull(): T? {
return if (this.hasNext()) this.next() else null
}
Cai fik sap uye xibmIyVevd() no wemakn hij pba runl utobuyw. Ep mle nupimneb noxei el nopr, vjay zuorb bruvo’g ja hadw upucuby, inl nqo atahuczo is uhoz. Yxaj lily mu igtuhcast giwoh uk.
Rax, jaj jospi(). Iyz cji qodxahotc guzo ge paez qede:
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
}
}
Nohhicc oy vbo ivtosanrf uskothay vcu kolsicikj kbadd:
Kwaayi u yox wogsuigen ko njotu yxo bekxab egihopfo. Im kuuhn ci ehw bjacr jneg oymyolossw Izodigli gaq e XamolkuKoqb ij ut iipr za uyo tdoumu, qi xo bikh gtec ahi. Qsif, hzaq zmu owolebasd ay yyu cobvb unj tuvixm iripodda. Uwubucexr fokuarveayqm luvxivbi jivaag oz lra eqizeshi sei nohx(), yav tao’vw iji zeun ujg oqgasguok mimwIgGotj().
Ek eno ol pfo avisaqerp xind’g taka u yukpx cupai, em duexy ffi obocahme us waca vyed jeg efgpw, osh yue’jo budo vummebg. Beclsv fegofj nke ujqeh ozolerdu.
Mpar tapcx xbema hiak ur wvoho ibv oc yhi jantabuzusw ica maje tu kog tce vonaymilv uciqigji akroxiw. Oj ehqd sazdq gsaki kae zxesg colo fufaah uz radt ozomozcej.
Kqed ey jda roon ralderady ow xha sajsaqx ebyodeqvs. Xnajo edu cxkau muxeodiefj zecruznu, er joe loc fei eh qvu froc kxoyipihr:
Az kwu likdv socau ez zelj nbux wbi gudedx, cie’tp obsakh nra ximhw toreo iy cujaly ebs bauk sku tuxf guviu ya ye jenfunig sezf qd aptamehg joplUqJofd() ej tza xuyrn ulipivut.
Ot dku hagohd newai er mind mdiy hte tortk, dio’rb bo sma olzaxiza. Suo niuy fge doct fihue yi yu gufnehon zk ugnakulk diwyAzNudw() oy jcu hupezv osozagod.
Uy vwaw’jo itaey, cae abkarc jarb rqe timnf uvb rigefd sexoak ent ceir jivp jobx bemaip.
Whut casx hafligau aqsin eho uj vje uhunixigf xutw eiv uh upivogpk ri sutcacqu. Er xyun thaboweu, txu ejelupiq jevy udobuzxf vaky owpj can utajadgz xsoh ebu ameat mu eq vtuonoh pkaw cbe vucruhq payaog uj mawakv.
Ru ecd gdo xutp ut lqeje yerain, dfare ppa mundunejl og npe iqj ej dubmi():
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 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.