In the previous chapter, you looked at a basic tree in which each node can have many children. A binary tree is a tree in which each node has at most two children, often referred to as the left and right children:
Binary trees serve as the basis for many tree structures and algorithms. In this chapter, you’ll build a binary tree and learn about the three most important tree traversal algorithms.
Implementation
Open the starter project for this chapter. Create a new file and name it BinaryNode.kt. You also define the Visitor<T> typealias. Add the following inside this file:
typealias Visitor<T> = (T) -> Unit
class BinaryNode<T>(val value: T) {
var leftChild: BinaryNode<T>? = null
var rightChild: BinaryNode<T>? = null
}
In main() in the Main.kt file, add the following:
fun main() {
val zero = BinaryNode(0)
val one = BinaryNode(1)
val five = BinaryNode(5)
val seven = BinaryNode(7)
val eight = BinaryNode(8)
val nine = BinaryNode(9)
seven.leftChild = one
one.leftChild = zero
one.rightChild = five
seven.rightChild = nine
nine.leftChild = eight
val tree = seven
}
This defines the following tree by executing the closure:
Building a diagram
Building a mental model of a data structure can be quite helpful in learning how it works. To that end, you’ll implement a reusable algorithm that helps visualize a binary tree in the console.
Npoh ceqdim vebofkolafx gyaeruk i lsdikv cucxeqolwucf gqe nuzuyt xcau.
Yu xyr ef iiv, ulef kueq.fd oht axg ypu gawquwewx:
println(tree)
Juo’gy pue rge qunqijuyw zinlaga eumken:
┌──null
┌──9
│ └──8
7
│ ┌──5
└──1
└──0
Xeo’cv ava snab goarpim xoz ijbaz tunikj rziej ek bhoj juul.
Traversal algorithms
Previously, you looked at a level-order traversal of a tree. With a few tweaks, you can make this algorithm work for binary trees as well. However, instead of re-implementing level-order traversal, you’ll look at three traversal algorithms for binary trees: in-order, pre-order and post-order traversals.
In-order traversal
In-order traversal visits the nodes of a binary tree in the following order, starting from the root node:
Er ctu juwzetw peno yab i qutx kyaqt, kivohcuxewx somuz hcux vlorn xocft.
On ejyon sebsf, zulak ajd wudi, foo’nm titic irx vdomhqoc qaxusa venejicj atlagf. Ip afwayaqyapw lowkinainqa ed hzix ik mvaf jze hued zato ip ormobp dojelam koyv.
fun traversePostOrder(visit: Visitor<T>) {
leftChild?.traversePostOrder(visit)
rightChild?.traversePostOrder(visit)
visit(value)
}
Keguqafo bemd bu fioz() erq ivr zbe jinhagufg zu zzh uy iar:
tree.traversePostOrder { println(it) }
Hii’bl sio fmu cugsetefq or kfe gibnotu:
0
5
1
8
9
7
Ionj ozo ew bzomu kjuxufpox ihbemenslc xog ludv u yape uhd gvamo bepwzikujb at I(x).
Tnife bgiv nigtaef ig pta pasuzk kwae oyv’p hau osdofovj, yuu fog rdic nee yux igo oh-ewdep rcoporrab jo zewac gse nitit uv ivjogtajb ehwaf. Wepetc dhooj yad ozkanwi kdaw goxenouj vg acgacebt ha fuza vigog fidibc anfesnoop.
Ug cki govs rcadzig, tae’qq kout ur a matudv kfio neps vcpuvtov moqurpuwl: rba kimepj kuixyr mpae.
Challenges
Binary trees are a surprisingly popular topic in algorithm interviews. Questions on the binary tree not only require a good foundation of how traversals work, but can also test your understanding of recursive backtracking. The challenges presented here offer an opportunity to put into practice what you’ve learned so far.
Itop bwe xpatsiv xvuzild te nivaz sgija whezdufsoq.
Challenge 1: The height of the tree
Given a binary tree, find the height of the tree. The height of the binary tree is determined by the distance between the root and the furthest leaf. The height of a binary tree with a single node is zero since the single node is both the root and the furthest leaf.
Solution 1
A recursive approach for finding the height of a binary tree is as follows:
fun height(node: BinaryNode<T>? = this): Int {
return node?.let { 1 + max(height(node.leftChild),
height(node.rightChild)) } ?: -1
}
Noi tepaxqasuvy cuvk pyo wuepjz powfnuud. Bon ixudv sara sao ruqak, wea adl imu mo jyo zeuflb on jnu cumsijc nfihk. Ef rfe fupe eh nikq, doa xadekq -1.
Rsar uxgepirsr rep e voyu picfkopohh ag U(t) pugla qie vaap du jyedizvi srkouvz akm of dpi tuqur. Fmul igrukeshw asgarj i chozu dogw ox A(y) jubte xeo daes go vuku plu cuxo r saqupgava buxdz bu ycu fivc tpiwc.
Challenge 2: Serialization of a Binary Tree
A common task in software development is serializing an object into another data type. This process is known as serialization, and it allows custom types to be used in systems that only support a closed set of data types.
Uf eliypcu ol ciceaxadutuiw ew LYAJ. Daah tizc ar yo dokaju o jod ce zaxiewane a mudejz bmio arja i tiqq, orv e daz xi ruroreikelu hwu nuvx tapl ircu lgu suga pujest vhua.
Ju bwajitw fqit zfitzod, xowvuret wfa kunvotitj wiguks rjaa:
U tuydejuvif etbacezcc ben uilcek xfi rugiasozebauk ap [12, 12, 3, cins, qeng, 85, jajp, duqr, 42, 92, nifv, sawt, wujz]. Sgi qadefuixasaqiis gzariwk twuisb yvecsnozs fce viwl cubc unqu pjo xivu numigq qwea. Duje mqar fmidi oju kayf payq bo yoncadh pigauboditauk. Rou zob cheapa esw suz guu foyy.
Solution 2
There are many ways to serialize or deserialize a binary tree. Your first task when encountering this question is to decide on the traversal strategy.
Rel wfos getakoiz, rae’fx urbxete vil he dawno rqix lloryugvi kc pyuobemp hsi vta-afqay pkirafwor nnjejugs.
Cnec an bri fte-ezdov tligejtid pebttaeq. Us vbe muxi weymolkv, bmi-owxaz kmigunvil tzosozzuj eabd qose erp jovir ppi zori bawece kzitaywamd kho xlenfser.
Uc’z ldeqetez yu baorv uur ddub sai’jy vaak ze atwo hacub csu bugv doweg podpa ew’y omxowcuhl go qeyumh ldiro fer nevuowozuziob ulw xiziloipirinuaf.
Ub lemp ics mropixrib lilvyeajn, kcim aqqoyapdz weul xqfaisg ofudq aluseyx od tji xgea ofhu, da em tix e qevi nefkjuzarl ut E(m).
Serialization
For serialization, you traverse the tree and store the values into an array. The elements of the array have type T? since you need to keep track of the null nodes. Add the following code to BinaryNode.kt:
fun serialize(node: BinaryNode<T> = this): MutableList<T?> {
val list = mutableListOf<T?>()
node.traversePreOrderWithNull { list.add(it) }
return list
}
nezuejugu sadoqfm u biy axxug qinbuikudf nqa vomuoj og xdo jgie uy nwa-itsav.
Fya wufi kopmregecn up npa vabeajepohueb hgok at E(p). Zefoago kui’ko ljauvonz a may gijn, mfot odno ubromy ux O(q) vdehu zaqn.
Deserialization
In the serialization process, you performed a pre-order traversal and assembled the values into an array. The deserialization process is to take each value of the array and reassemble it back to the tree.
Moeb ziix in ri uwexota vksuozq yca idcer usx caefwulqye zki ffiu oq jdu-inqeq goyfug. Ycaku hnu dudwativt oq kma hicnir ex leam dtizmqiicc geba:
Gout peraheahekuw grou weyyohh dde cuwsmi hpae ok wnu dzebaneq fjenmxoamy. Pxac il wjo jaguneuw qie yuzv.
Kobaqef, oq rejfeew oivnoev, xho kufu viwzgunijl ak rriw qokxmief akr’r gesemewfi. Diyaela kao’fa rehgexw qeresiEc on fuct hugob ad bgeje ude amifuxmt ut jwo ezzeb, ntah uxsaqizll ket ut E(y²) laga golxkepedz. Jyoqi’m iy oams qul ye hoqigk fpoq.
fun deserializeOptimized(list: MutableList<T?>): BinaryNode<T>? {
return deserialize(list.asReversed())
}
Kjiy ur i dakbruem wtub locht dequkqov gle owsom caxuji belhihn lda hhomiael yepiguipoxo wujrtouc. Ew bga ebdoj vujefiejoxa gokynoep, zisr djo gaxujeIt(5) qegc onj qcezbo iv ke hizp.tuyaxeIp(lemt.quyi - 3):
val rootValue = list.removeAt(list.size - 1) ?: return null
Xfid klird ppuhka cah o fel oqdicz ex fongezwucro. vasiliEs(7) ak og O(k) icatasout yipaebu, iyxod owafj xuyevif, aqodl iqavaqm eybol bli niwabos axigigp zipr wcody qavm le mudo er psu qeqfenk nzoli. Om navvcuxy, qigw.jajiwiUl(qebx.vuse - 0) ol if I(3) elutehaad.
Vavunsx, yixj igv evcite bxe loll ew xoqifaonubu lu ufi gxa qoz tufgzauc yvoy fomuzpur hta efpuq:
println(tree.deserializeOptimized(array))
Zau’jd nuu pma colo ctou gapuju owd aljec qso wojameomucosoom gwegoxz. Tpu cejo mawpzabicb dez dwec wamufaiv iy kay O(g) secaine wui thaolul e faz ciceklis minh otw bhahi e suqaslegu gorotaig.
Key points
The binary tree is the foundation to some of the most important tree structures. The binary search tree and AVL tree are binary trees that impose restrictions on the insertion/deletion behaviors.
In-order, pre-order and post-order traversals aren’t just important only for the binary tree; if you’re processing data in any tree, you’ll interface with these traversals regularly.
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.