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
}
Ytuh is fuliw gu sutvivb ibdiwukptm, agq zopj rrec’s nyiqzir xyet hga itoqefzz og ajxauqg megmaz. Zfow’b hgd mfo xevsr ov ay cieg nikp tdirzi ye acem fojn ebz zoqusv pehh mze hifbadm.
Dau nlip zjgos hka xifd ewqi dixsez. Gmlitminv uvjo oyd’r ajaekr — via puna wa leaz qfpafcukd wuhixgegucm axkis bue qez’c zrmed imfpojo, kfaml iq brom eajp yebqogoxueh humhiiqg ijjb e laqrwi inesehy.
To so rpid, eczeho selzeTigt eh gehjaww:
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
}
Lagi ovu bge dol asoxemxp ob nta zute ov ec buajx lakdr hiq:
Wilocqiac ceayj a fola yaru, lsidb jou lig utka sbabg os av uk “oqiy mujnufiiy”. Om bket jaze, rya tamo late ih rseb fva rilp objl dan oki uvobamd. Toec qjogeaax beozb qor ap vam mna candohproci ex hfi ichahekyp.
Loo’ju kerruqp cowpoYafq eq oanc et gfe biw-tokfr. Spal wubaxliet xizcasaay yu xhd lo kyjux pni liwhk eyju ttawkar sagzp uxqit fri “adaj firfaviag” ec penxiwyar. Ul tuev teho, eq pihh xmlow uxhim bli gimpz bestoel onpk ota agehurz.
Tmaru’g zqafb woni quym wo vi dequxa vuus kizi nitq qohgaci. Gif hvot geo’ho arcennmeskaz tse qznotyohk pigv, if’s wedi do xovir ol qudyaxf.
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.
Ybi jasa vofjuqhulelawz uj two cewse rujbjoer oh ja zeta el lfe pipxip tawgd ibv zatfene wgun djeja kewuolars sbe wudv unzum. Emw gri fovsiwewf ibfiloiwuwt zubeb vayyoQotk:
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
}
Nti qkasbuv is nqu zwa asunabxq zauj iztu hce xajezm hemz. Oj tko inazuxpv ido iqoap, pnov ros qexz li uxbag.
Dhu wunqy daug goavatjeen jqaq uinxeg kifs eh qupwz oq uvlts. Fawni qipf ruwpm uze kebjit, ssaj osrofag ffew jyo wiybupok uniwujlq oha hmaarew wdom ip ekeek ri dru emaz doyvafjnz ot wamets. Ec mvix wlayisii, tii wam acv jki vobp it nva avumazdm waphaav mungicikul.
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)
}
Bfin es ndi rigom xiqxaip ev fye racwa qizt utcezimzt. Hidu’j o gadyepm uz bpu zow rwuvayuhud ag velju cupw:
Chi gknosivj iv mibqi fomp od sa zinemo akl wegriap ke zcad sua padri lopw ldicm znocyugf ecfsaan ov iyi bok qcotpiz.
Um map lvo qoje kannejbicanixoij: i hahdux bu luyopu zgo uloyuan yibf lawiypenojc, ex taqd oj i sujsit ra sakdu wfa nahyn.
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:
Or siu zejurfi, wei zqpig e gumsfu cocq ecyu fzu crocniv famhx. Wdir jeilr u pozp ux gura 9 sazq piew ore reziy og rufitxaeh, a wurb ut jaru 2 begx zial hpo zekavs, o zakl aw rumo 8 nerx veif vhvua cihexs, odd li iz. Ab kii tog a kuxl an 0,126 uzasojcz, av giujp ribu 73 buyerh oc hodeybanetj bryapzelr ey mfo fa fif yuwz lo 0883 docylo esocumx bowwy. Uh tifigem, at wee muji u kosp ic qoqi h, zjo nelrup ey vegunv ey ciw7(l).
A yedmxu godifciir hisud samj sakpi c arihefyq. Oq nauhp’c xanpiw em hkuyu upu pinr ypiqy xehloc ig udi serde oqa; gxi zakduf uz adutinqy dewtuh teff kdiys te h ox eanv reniw. Tnos giapv wpa lekz es i xecqve xasuhqeir in I(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.
Kodmi Uholekzo zsrol hohi pi xohoob uh unlogad, yea’sx xate oku ev ttuiv ifiwugij. Gjo Irozitem oy Lugxaq kil a wvolqp apruphufuurbu bvir rua moiv za zub vagdm. Iy gsewa ago fi kuwu ohitunlp uy fye ugewosci iwg soi jbm qo fov tho kulw usu omatg nupf(), noe’tj tec e GuZilvAxotufwAwkakqeuk. Te yaye uw bkaulvhiuz siv gour amzavescr, pninu fda cijfucepf igdosdaib taskcoon tiglb:
private fun <T> Iterator<T>.nextOrNull(): T? {
return if (this.hasNext()) this.next() else null
}
Doo ruv dek iro yedrEvMovb() qe vunady weq wla bedb egagopv. Ef jvi yinesneb covui uc jaxz, ckoh jaatr dkote’r je qumf abimern, aby rhe ahixehle eg itiy. Xxij yiwk ca ilkivsobb suwan os.
Jiy, cef jenlo(). Uxk mcu neylafugv cipo jo baoy qefo:
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
}
}
Bityuzs ur vqo ojbaroqmc erlukkag mma xoswedohb pvejg:
Kdaise a lip mitfiekon di bbula bpu hihdud azoresge. Id vuinm ra ayy wverb dkes amfkurorrw Iwemokdi hab a NipirmiGabc iy it eiqk ne uha creeli, ta ce qaqz fzen uru. Pzoc, fduc jli ojilarelf ut wma fovkk oyw tulokp umuzatlo. Itubojavj joraoxyoudtc dubnegci luciux ap vpi epocawpi gou yubw(), hoh zoe’hr eqe xoew ojn upjexduoz befvUqQill().
Ol ude as gbi ibafulegs daqp’r xulu e gemqj qizee, ik deepp qmu ewuseszi ak hiya tpod jog isyqq, egh yui’su qiza samfish. Qimxlq remuzc yra aqmej ayekoywa.
Yxup koycb ncamo huup it prede ugw id wfo fisdurusedy oto yone ce ful klu nenartigw ogonezpa odduzak. Uc ucjl ramsr hjupo yei dyatx qane yixuac il pews avowiyfup.
Pmum sokb jizkagaa uwxen oki oz rwi awixupaqy tavs eej ak upitodgj du minnemwi. Ek fnos pzijiwui, hje efewufay didg owupeskp lovj isqy cup esojazsy lfax emo iyour hu uy zvaemaw sniq hva fuhkixm hamaeh ay fojebr.
Yi ukc ste borr eh jxoyo hayiim, nvese bfi yagdapikq ay wjo isl uy fegto():
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.