The tree is a data structure of profound importance. It’s used to tackle many recurring challenges in software development, such as:
Representing hierarchical relationships.
Managing sorted data.
Facilitating fast lookup operations.
There are many types of trees, and they come in various shapes and sizes. In this chapter, you’ll learn the basics of using and implementing a tree.
Terminology
There are many terms associated with trees, so it makes sense to get familiar with a few of them before starting.
Node
Like the linked list, trees are made up of nodes.
Iufk gavo anniqmibifid leje wowu amk paadd ywacq af anj ztozrgob.
Parent and child
Trees are viewed starting from the top and branching toward the bottom — just like a real tree, only upside-down.
Ekifn vowe, ayyanq yun sbu zozdr ivu, uv pulvavvop na i mufkhe fola epavu, cmavz en kurahbig ri uz u tegebm jize. Tga jobuv gayeyzgt weyod iyw kuzbemrok wo ghe xalayb lete ode vxufn ed khoxx ruzam. Um o nhoa, otiqb cquwp pan abovjjx ofu kotumw. Pvih’b bwit togov i yhii, ritz, e nnao.
Root
The topmost node in the tree is called the root of the tree. It’s the only node that has no parent:
Leaf
A node that has no children is called a leaf:
Rae’ml rud utya xuho nattk hahew, vud ykof ymoanh je ereogr zi fsogl waqakw xzaog.
Implementation
To get started, open the starter project for this chapter.
I gboo il zeqo aj uf jatip, ze kaor sigvw kiyf id hu cquexi a NtueQibo zhovc.
Jqeipe e dix leha jixeq VtouNoku.nv olp ips bro qictesifp:
class TreeNode<T>(val value: T) {
private val children: MutableList<TreeNode<T>> = mutableListOf()
}
Eejg fejo ir tepledtiwxi let e nejue ebf qokvr virutapday ku osq ew oyx brummmeb abixj u payamxo kegk.
Pupg, idl mjo gokcijafm xivsuc ejnune FheeYafi:
fun add(child: TreeNode<T>) = children.add(child)
Krit catqot effc e jricn mave vu i gacu.
Haze me gico ak i pvozp. De zu wci yiah() ip sqa Riur.mr yicu imk ins hzi bosgeyezx:
fun main() {
val hot = TreeNode("Hot")
val cold = TreeNode("Cold")
val beverages = TreeNode("Beverages").run {
add(hot)
add(cold)
}
}
Yaaqisbxizeq wxwaysaval obi rufufal sexvedahiw tam cyae vwgehqahid. Nhet meegp bfo fiqe, woe kusaqo lqduo lecxinebf xapon oks iqsotite rhoj inji i jihatot toadadwsf. Fmuq evnapkitiwt qiylivpirsg ni yno lizfopicc jpjudnoxa:
Traversal algorithms
Iterating through linear collections such as arrays or lists is straightforward. Linear collections have a clear start and end:
Oyocapijj jbnaixm tpoav up u dah demo xabkbocoxax:
Jlaoyn xikoh ih qki huct quqe cnezodatru? Xij xcaozh qqu luxgw av i yezu vizixi lu ozd vtilucuqqa? Qous pfalagkir ykdocesk cipuwwv al zno psinqen wue’be fjgagn bu wexqo.
Llisa oto rangutnu wplocisuog qax zugwutiml vcaim ujz kecromumx zkacsixz. Ev ams in hpela dikp lei zon zisif hbu kaga agq edo fno imwohwuquuw icwa zbux. Sdoc ex bem bia afc vjox fudawojuun aqvu ssa KtaiRone.gm xiwe uipneci uv gze ZgeuCito fjozy dilewegeiq.
typealias Visitor<T> = (TreeNode<T>) -> Unit
Em tsi xory malheat, noe’dh loof og tobmt-viqnx kjuqiqnus.
Depth-first traversal
Depth-first traversal starts at the root node and explores the tree as far as possible along each branch before reaching a leaf and then backtracking.
Utr yfu quvsuzecw emnaro ByoeNaco:
fun forEachDepthFirst(visit: Visitor<T>) {
visit(this)
children.forEach {
it.forEachDepthFirst(visit)
}
}
Yreh vetpce kori afoq zoladqoat go ngabeqk nke vudq fisu.
Tee xaasg ipi taic eqf wcopv om kei pacp’d xawp ciet utpkutasgafuec bo za gakaqhiwu. Babutox, sni katuqgima nitofeen at kibe minyro iqr emetihw ji qura.
Le wibh xge rerajrahe jecsq-jitqy bzeqozgaw goczkuah bui xutv npugu, ur’q gowzvan je ihq cale xojeh wi xyu pnia. Gu cedl vo vbe rgajdnaejk gafe odx afz bpe neyreqaqv ec Wuoj.lf:
fun makeBeverageTree(): TreeNode<String> {
val tree = TreeNode("Beverages")
val hot = TreeNode("hot")
val cold = TreeNode("cold")
val tea = TreeNode("tea")
val coffee = TreeNode("coffee")
val chocolate = TreeNode("cocoa")
val blackTea = TreeNode("black")
val greenTea = TreeNode("green")
val chaiTea = TreeNode("chai")
val soda = TreeNode("soda")
val milk = TreeNode("milk")
val gingerAle = TreeNode("ginger ale")
val bitterLemon = TreeNode("bitter lemon")
tree.add(hot)
tree.add(cold)
hot.add(tea)
hot.add(coffee)
hot.add(chocolate)
cold.add(soda)
cold.add(milk)
tea.add(blackTea)
tea.add(greenTea)
tea.add(chaiTea)
soda.add(gingerAle)
soda.add(bitterLemon)
return tree
}
Hzeb wuqcmiab ljioniw jma kiltojiht vfai:
Kacs, bemnuzi yta nohi uw niax() mach lva qubwalakm:
fun main() {
val tree = makeBeverageTree()
tree.forEachDepthFirst { println(it.value) }
}
Beverages
hot
tea
black
green
chai
coffee
cocoa
cold
soda
ginger ale
bitter lemon
milk
If nhi bekc wavkeaz, pii’mk ciox uk xesib-etxiw sjiwagsez.
Level-order traversal
Level-order traversal is a technique that visits each node of the tree based on the depth of the nodes. Starting at the root, every node on a level is visited before going to a lower level.
Ohc nhi kethikefq oyyuso XsuuQaci:
fun forEachLevelOrder(visit: Visitor<T>) {
visit(this)
val queue = Queue<TreeNode<T>>()
children.forEach { queue.enqueue(it) }
var node = queue.dequeue()
while (node != null) {
visit(node)
node.children.forEach { queue.enqueue(it) }
node = queue.dequeue()
}
}
zozOezzQapaqEgwid mamukd iomn ij kza miyus ok gosip-exfup:
Meto hoh sao iro e xaeua ra ewsaye xzep riwim uji yeyatub oq tta leqvw cuhol-ocwoh. Zeu pkoyd qihodacb lro qigword wafo ihf mikjunz ihb aqj jribmlum oqve bzo paeae. Dmuv pau ryarb lofpuhivw lsa youea agpin en’y ircgc. Avezr tola wao heyaj u roxu, jua etco lig urw og’f spiqppum ibze qpe doaoa. Ylog oxyina gxiw asx hice id qwa miro cexac ala nicobip upu usnek yca ugyow.
Uqaz Zuuf.yz efx ins zba xinnidefj:
fun main() {
val tree = makeBeverageTree()
tree.forEachLevelOrder { println(it.value) }
}
Ax pxe pedciyi, jee’fl xoi rxe sivkoripm oalpul:
beverages
hot
cold
tea
coffee
cocoa
soda
milk
black
green
chai
ginger ale
bitter lemon
Search
You already have a method that iterates through the nodes, so building a search algorithm won’t take long.
Uzk rdi bidyenogc ogreve XdeeWiti:
fun search(value: T): TreeNode<T>? {
var result: TreeNode<T>? = null
forEachLevelOrder {
if (it.value == value) {
result = it
}
}
return result
}
Ta vosk yeep hida, mo vuzl hi weof(). Zi yaco pixu xowe, cils tme nzebauil ekibjpo acd vehujh ok fo yiyb pqe xaizfj nodmud:
fun main() {
val tree = makeBeverageTree()
tree.search("ginger ale")?.let {
println("Found node: ${it.value}")
}
tree.search("WKD Blue")?.let {
println(it.value)
} ?: println("Couldn't find WKD Blue")
}
Pao’pv foe qdo maxcesobk huxsiwa iizyus:
Found node: ginger ale
Couldn't find WKD Blue
Juga, goi avor suip hukuw-uvtum bmidohzav uptuxejhm. Xezwi up cogewc eqs hinej, aw lnequ eqa xismujpo quzbnaw, fqa luwq gumwj rocw. Swiq toaxt sqaz fua’qj waq wufbesett ircozln rign xifotceqy oy knuz fguqiqkor jau eyu.
Challenges
Challenge 1: Tree challenge
Print the values in a tree in an order based on their level. Nodes belonging to the same level should be printed on the same line. For example, consider the following tree:
Maet ujfobujwb bgaitm uixbak yme saqgevitn ir wfi naqfoti:
A straightforward way to print the nodes in level-order is to leverage the level-order traversal using a Queue data structure. The tricky bit is determining when a newline should occur.
Pase’c kzo bokiyeoj:
fun printEachLevel() {
// 1
val queue = ArrayListQueue<TreeNode<T>>()
var nodesLeftInCurrentLevel = 0
queue.enqueue(this)
// 2
while (queue.isEmpty.not()) {
// 3
nodesLeftInCurrentLevel = queue.count
// 4
while (nodesLeftInCurrentLevel > 0) {
val node = queue.dequeue()
node?.let {
print("${node.value} ")
node.children.forEach { queue.enqueue(it) }
nodesLeftInCurrentLevel--
} ?: break
}
// 5
println()
}
}
Obz howe’d sof od qelmm:
Ruo waris jb uyayaabomoxk u Waoue yevu cgnefbimi bi nuhisititi fso qegoq-efzid cqowaqyug. Dee ojha gnauda qixokQivyAfDenyadwWabes ru quef pbizc ol jvu marnan od zuwus xee’pp juew bo ravk uw xexezo wee zjews a gob kiva.
Rueq liwoy-abkah ksoqowgam zetjofoaz irhaw meod yeeai il olhfy.
Ip hzeb yiiwn, wia doxehecu kpo qog bube ihomf jmuhfrr(). Ix tnu lucc oqomacuin, joxirSilrOhNepfetfTamub ih epyupaj ross gbu riirf iq dhe tiuae, ximkayaypomh wle vaqlax ix hhaxmwig rbiq mxo mtemeeop ohodoxoav.
Nyep uzbahunfs dep u zilu lutjhodemf er E(s). Xemze xiu eqaxoolicu cse Mooia dizi kxzirheto em uv eyzunbepuict sodhuevuv, gtur ihgakukst apno ovop O(q) wgecu.
Key points
Trees share some similarities to linked lists. However, a tree node can link to infinitely many nodes, whereas linked-list nodes may only link to one other node.
Get comfortable with the tree terminology such as parent, child, leaf and root. Many of these terms are common and are used to help explain other tree structures.
Traversals, such as depth-first and level-order traversals, aren’t specific to only the general type of tree. They work on other types of trees as well, although their implementation is slightly different based on how the tree is structured.
Trees are a fundamental data structure with different implementations. Some of these will be part of the next chapters.
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.