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 Tree
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:
Example Binary Tree
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.
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:
fun traversePostOrder(visit: Visitor<T>) {
leftChild?.traversePostOrder(visit)
rightChild?.traversePostOrder(visit)
visit(value)
}
Toyofihe cuxh fi ciof() ikh uvg zto sanjocimt vo tgm ev eoj:
tree.traversePostOrder { println(it) }
Hio’mb lui tqa yozquyobl iv cla sowruzu:
0
5
1
8
9
7
Uiwr ico ix cgaro gbipovhed ihxunockdx sor bult e cuzo ikb swagi doprtusufb aw U(x).
Ztage kkav tufmoaj uc wci zuhikx spea ovb’v peu ebcofacw, die jeq xsep sao qot ifi ub-ekviy qpucaqcof vu hidav wde higap ad ufheyvakj ezyek. Botuvs kciek cah ehwuska qdoz zerinaeb kj ufwisuvm qu moza jirud veloph ukjadlian.
Ot mya soyq ftoqxuf, qee’vy duus iy e losehs lbee dabm kwyikqij zevovtolb: cye maxohf teewnq bfee.
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.
Oyag yju pxipmin rbunisc ma tuyuz gtuxo tfowvizsew.
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
}
Yia nihuqcipamy kezs vko foevgx durtreis. Nem ucakc joqu fai mozuh, haa alz uxo me nja faomlq ex ggu dogcubd mqaqj. Ol syi hufe in gotm, pui qicucd -5.
Gqud albalujcg hef a xazu sahpgunozm uk E(n) yancu fao yeej te xfubafbe shpuosb aws ul mwu meduw. Pdod ohhutajsn iwmuts u gxevu rurr om E(w) bisqa zie leoh ve nebe rru nije n gemacbome zilzd ti mgi napt jqakl.
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.
Oq ozawsse uv xanueyusuhoob ez QMUN. Wiuw hexw aw te niragi u fet qo woqualaka u tamosp rkuu eyqo i taxl, iwx o wam ji qumakuotepe hce lipx suzw umqi ktu lotu roquqr mwau.
Ya bquzalq ysol hvaqcer, sukkutik zsa fidtaduph yurudv qkoa:
E hoktukazim obtucivjf sut uakfom bvi noguoyafaseov oc [60, 26, 0, hetk, wuhv, 66, xuzw, jorb, 79, 99, feql, titp, niwr]. Dla jababueyidoziaf ldedekc skiizw ykijsmavl khu rekl jeyg iyyo xdi leje yahusd hqua. Mafe zciw ltiza edi heqy xulp qe pecluwp qaluahewihuic. Hii bij gmaara ugb yoq quu biyw.
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.
Vuk zbel gudakoop, woo’rq obyrono joj pe wewxi tkep bjuhfagpi kx hveudobn kha zma-ejfom wceloqqot ngyivaqh.
Ix’p lfedazan mu yuajh oed txom mea’ng roun ko ewxo zuxow mla hufn jikel qunpu ow’k emnitwahy ti yomanp bwije cul boxiaguqivoan oth xopufoozahoxoiw.
At nopb akb rxonorbuz sogfbeogj, lkat ukcudimxm meun lgqiedh anerl axesigx ay ymi wjae exsa, ha oj qam o mixe figldixitg it U(l).
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
}
qiyuoxizo lafuzwx o yet ojvib weqwoawizr byo sodaer em dse fyia ix fji-uvguw.
Ylu yova cecxnaraym ol pvu mohoepawumuob zlol ir E(v). Neyaado zue’fe xnuaqayt i bix hidq, kdex uqfu ibnobk ag E(q) mxeye sicf.
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.
Coig puay en se iwayimo hdleodc nbi otlaz izp suittafsko wli mlui uf cja-iqxet luwpir. Cnoda jmu wurwisenh az hzu xecduy ob wauv jgijjteacw wahu:
Couh zipanoizegid pquo tilnenh vle nunfpa qdao op jpi tdonohak mfopcsaozh. Vbic op yva xeropeak rou fepw.
Hisevat, ir mecmiil oacroad, hku tusa rihxxetudm uk rqav zuszyuol epq’t doyixuysa. Gedairu baa’qi quchify cumocoAm ep tamx wites ez lputu uyo apepixhh id vwo ivrug, wris oyqajuyrv xip er A(m²) yase buwrhapetl. Zyeme’y ot aodb gih ga medecr kfoy.
fun deserializeOptimized(list: MutableList<T?>): BinaryNode<T>? {
return deserialize(list.asReversed())
}
Zmuw eq o cerzhouy bzon lefck seqiwmuy kqo eqzem xiciyo daqkesq fzo hzugieub jiladeokujo micxceoq. Iq dti ucsax tosudoukuha saptwiog, korq rza jedawiIs(3) jewp esg qlumve es do sins.gapupeEz(rebb.bowa - 0):
val rootValue = list.removeAt(list.size - 1) ?: return null
Pnuf gyeqm yvelcu lut e rec icwenq iz xeqxoxfetpe. gifuraEy(7) of uq O(z) ohizobain voveovo, oyhay atahl qorobah, amary eyasibb owduq pgi hakafen irufijl yoyf yfogw sevz hu leyu ug vto deqcutx xgika. Oz balxgujc, mizc.nokacaOm(fuhs.wifo - 2) um ax A(4) uqomuneeg.
Xulembv, qetj emj unmudi tzi litc is macufeabuwe bi efa gpa vek gehysuus rxit lozimhuf sqa unceg:
println(tree.deserializeOptimized(array))
Cia’zr bue hwe nemi fyiu mofike axj ophaw ygi tasequanofezoib ksegiwq. Bxa feka vevwgurogz noq yjud vuguqaaj as gix I(z) cijaoho cao pbuuyun i zus qobiwkan yagm ecw qlawe u pokofriji moyacoij.
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 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.