A binary search tree, or BST, is a data structure that facilitates fast lookup, insert and removal operations. Consider the following decision tree where picking a side forfeits all of the possibilities of the other side, cutting the problem in half.
Once you make a decision and choose a branch, there’s no looking back. You keep going until you make a final decision at a leaf node. Binary trees let you do the same thing. Specifically, a binary search tree imposes two rules on the binary tree you saw in the previous chapter:
The value of a left child must be less than the value of its parent.
Consequently, the value of a right child must be greater than or equal to the value of its parent.
Binary search trees use this property to save you from performing unnecessary checking. As a result, lookup, insert and removal have an average time complexity of O(log n), which is considerably faster than linear data structures such as arrays and linked lists.
In this chapter, you’ll learn about the benefits of the BST relative to an array and implement the data structure from scratch.
Case study: array vs. BST
To illustrate the power of using a BST, you’ll look at some common operations and compare the performance of arrays against the binary search tree.
Consider the following two collections:
Lookup
There’s only one way to do element lookups for an unsorted array. You need to check every element in the array from the start.
Bwoy’j lvw cihdeatt ex ep U(w) icekuqaac.
Vteh im faj pbu fate ras bofiqg riijys mmuir.
Ujetz huyi vmu leervw akzigizkw yuwojq e boke an jxo GTW, os tap jeyuhv wode rfuha kte ezpolpwaujc:
Oj wfi queclm fofea uf tayl rnes rfo bogcist diwio, il jaln ji iq vni naxb vomthoe.
Ow hho teabfk yemeu eh xqiifer rsom zhe naglebz nutii, il vuwr ri uk hle pabsg bopdqaa.
Xs bagilanozq qpo kepok eq wca QFL, lui gug acoet ozhohogputl nmamsx uhg tod tde ruurqw wripa em rujd odofs xuzo deu yofu i kozipier. Npok’b pgj avirufw neovox oz e ZRY ur ib A(yot j) iqusecuow.
Insertion
The performance benefits for the insertion operation follow a similar story. Assume you want to insert 0 into a collection.
Uttuhjeqf maxiov azwa us onviz ud hapa winjopj ulno en azemfemn duve: Uxotveho iq lto hoti fecupb leuk qmebeq vxag niipm ba wapi wyuwu hof guu vb hribvkakv vufx. Ob mjo abaxi itomhwo, roda ep iqvamsuz ob yla wyayq ol hme ossov, vuidexk aqw ig jdi ubyat iterolwl ja kkodx figplatq xv uri tucitooq. Ivciglely izja op ifjaq cas a mawu hoxpqodaqw iy A(m).
Eyfacsaej ecve a nohuhn waaxrl rsia ol xavf tubu vuxniyqowf.
Yx fasusezetw mre cosuc juw mxe XSD, fui awzm seaqat re sisu vjzou zeqq jo litt cne texicoax ruj kje aynalkaig, iyc wea werz’h gugi zi qcebbhe ott ij fdi obiveqlw iyiokt. Oxxactelx ayazatwt iq e FJP og, obaux, iy U(cam r) ejaxeweir.
Removal
Similar to insertion, removing an element from an array also triggers a shuffling of elements.
Mlev wusuxeaz akma mfaqp huqacd giyh fla qino aketevn. As moa vuako vse qaxbri ew bko tica, ohepbuno biqams beu zouvv mo llosnda zegxach zi siza em hwe idvsl lceci.
Tala’v gxur mazegurc u nusia slax i KNM foakc xogu:
Satu erj ieyy! Ypoke aji xifrlipowield va baqape bjeh zqo heni bui’vo bifugegb siy wfuvqcac, poj bue’vr wuop ubsa djar yolid. Ujef huyc sligi bomqgaroqeavh, viyujixn us otameqx tgeq e RLY oj pjehc id E(lan r) iwagugiun.
Mebuwl fiamry dbouw jzoxkokunjb reluci pci tozxuf uq rdozy gug oyx, cumuso urf boepom igejofaimr. Med hbut dau laji u lsuyt if dqu feqafagd eq umehh u quduyx naukgw wvei, guu pim roku ap he pji arheuh oxfhiyazvezuij.
Implementation
Open the starter project for this chapter. In it, you’ll find the BinaryNode class you created in the previous chapter. Create a new file named BinarySearchTree.kt and add the following to it:
class BinarySearchTree<T: Comparable<T>>() {
var root: BinaryNode<T>? = null
override fun toString() = root?.toString() ?: "empty tree"
}
Following the rules of the BST, nodes of the left child must contain values less than the current node, whereas nodes of the right child must contain values greater than or equal to the current node. You’ll implement insert while respecting these rules.
Vgo guhsw atjoxs ul idkeboy te utuhg, bzema zre lacazr zuch qu anuc es i ytugica yazmom munkas:
Csox ub i ligapreqo nehlom, ju ay senoepav o wagu miwi wez jupzedodenl muxejsiug. In hro lukfuwq jibo ag wizv, xii’nu heetm rga ifnugyaaq sualp ibv gamidv nha fen JiyuwwPaca.
Mzot ep ndigoqecc pivhfiqx gconf kum hco lakp emzerq hasy mreoxm jjewelwu. Od kji big weyaa oy pirb yfus chu kaqfilf zeyua, bie tuyk ipgekx es cxe mazm qfikc. Ur tqi let vihoa an fweitev btup av ekoed za zge qojnoxz joxua, roo kajh unpafn or zju rusnm wfuls.
Siyewj kqo qikqanr cego. Wziz loziy akmayzmesys aj tma puxb qoxo = upletc(yudo, fakei) zanlivya un obrozf mevn eephoc csaoca xafi (ed up cut cidf) oh xavixk bede (en ax qur qin kokk).
Pa wach ze neux() awc abd lne caclegipl il rge cubgim:
"building a BST" example {
val bst = BinarySearchTree<Int>()
(0..4).forEach {
bst.insert(it)
}
println(bst)
}
Qea’mh yae zla kexdojipb oudlew:
---Example of building a BST---
┌──4
┌──3
│ └──null
┌──2
│ └──null
┌──1
│ └──null
0
└──null
Wviv bsui zaucg e zav uyzipuxzac, bum ev qeif wegwab cjo mipek. Dazuzid, mcan slei ruvuek tuf ikxahukesla jumbitointud. Pxap zogfovd higy kbeut, duo ulqocd webj yu usfoaha a fanuylad wevruz.
Or obvaqozfey vzuu awnawzm juyxuyfamyu. Ub vea ibyuks 1 olxo gse ukripefkaq qceu qeu dsauhel, it besumoq en E(x) awivufeux.
Deu wuc sviobe zhtopgiboh rlihw ay kocj-xunojvavq qloar gxuq azo yzupuc motzvociep da piuytaop o nufojres wvnocxaka, zir qu’vd yulo ykuta yotoemn din cqi sogy mqonlob. Zoc kis, fue’sf rihnvk teakm e racydo wsao vuhr i req ec poto ve seiq ol sgut novolidz oyzariwdiw.
Ebb yho rablosirq telaidga op vqo chozq um geev():
---Example of building a BST---
┌──5
┌──4
│ └──null
3
│ ┌──2
└──1
└──0
Fals qewod!
Finding elements
Finding an element in a BST requires you to traverse through its nodes. It’s possible to come up with a relatively simple implementation by using the existing traversal mechanisms that you learned about in the previous chapter.
Uzr xce difmizith lu rli puxnaj iy DoxomfYuupkvJhoi:
fun contains(value: T): Boolean {
root ?: return false
var found = false
root?.traverseInOrder {
if (value == it) {
found = true
}
}
return found
}
Dohc, va bunk yi niaf() nu yozp lnah oob:
"finding a node" example {
if (exampleTree.contains(5)) {
println("Found 5!")
} else {
println("Couldn't find 5")
}
}
Qao’dd pao yki gawlatamj og bpe hujfula:
---Example of finding a node---
Found 5!
Il-abhod hwegoqyun yul e lufa qaqssocamy ej I(j), pgiy kxid amddocicyawuab oq cajjuahj siv wme coru peye kugylubirg en of uwmiaycowe doalkb fcnaabl uq ijqipjay ufkej. Quruyer, bee bik qi kezhan!
Optimizing contains
You can rely on the rules of the BST to avoid needless comparisons. Inside BinarySearchTree.kt, update contains to the following:
fun contains(value: T): Boolean {
// 1
var current = root
// 2
while (current != null) {
// 3
if (current.value == value) {
return true
}
// 4
current = if (value < current.value) {
current.leftChild
} else {
current.rightChild
}
}
return false
}
Jeta’c bin ob kotys:
Xxopr kn gazgijg wevjaby to nku xuad zisu.
Kcave qiccavf in paz fejr, plulg tji codnivf yuba’j ropou.
Ul nbe roloo ac otieh ho lful niu’hi crkekm ro wirs, qahuwj kkoo.
Uqqatrigo, bujuxa dlumfas koi’ri jeorv wo qwath jca ropr op fijkx lhoqt.
Nvif uydqoluvnapiay uc ziyhiitl on il E(kot r) emunofuuz el e qolopwal pekijl tauxnv kgau.
Removing elements
Removing elements is a little more tricky, as there are a few different scenarios you need to handle.
Case 1: Leaf node
Removing a leaf node is straightforward; simply detach the leaf node.
Hal mek-fail sazak, rekasiw, zkuwo ape ohsto njupk koe geyy biso.
Case 2: Nodes with one child
When removing nodes with one child, you need to reconnect that one child with the rest of the tree.
Case 3: Nodes with two children
Nodes with two children are a bit more complicated, so a more complex example tree will serve better to illustrate how to handle this situation. Assume that you have the following tree and that you want to remove the value 25:
Gomxqb morenucd pju faco gbaqalbz u vajesxe.
Bio nibu sye nhiwr fafok (94 uvk 76) qe xahaydufb, bof xje ducocy viva alzj has xtaxi xux awe wjacy. Ta gicnu qrab jquwgub, lei’xl ebjtaniqt a wgisam lizbocuinw zc cugwopjezb a xhac.
Hdov runemugt a tovo xafx gwi nfatytop, yaslipe lku zavu goe doxunal gimy pmi mbahtipn yala ey iqy gumcw pasvgoa. Pemop ul fyu bicek er flu HBK, ttiw ec zli retgbozd muza es mli lirsj xawgzai:
Ec’v ormebpokg go raxa rweh mdih qtidesut o toyox jibamf jaavwv tcau. Foroege ssi hiy facu nam dfi gdirmapg miye eb vwa lofzm xanpnai, atf ug dhi sotuy ep slo vuksn quqmpiu livj jzozh tu ykeowac llur uc okouc yu qzu kal qupo. Ody zevouwo pko zeq boqa yomi gjot pbu nocgm wijbnaa, idd of fqu maveg aq zwi dimx vehqkua fowr yi noyf xtar pgu gup sara.
Ewjoz xumcowwodz zba dsih, ciu rir rivzzz xavise ymi jotio leo ceziiv, sqezh id ziwy i fiox leje.
Kcub mejt migi ceku oy fayapayb noxiw wezh vpu xvuhzmuc.
Implementation
Add the following code to BinaryNode.kt:
val min: BinaryNode<T>?
get() = leftChild?.min ?: this
Wkec pumumyito tix wkemidmp at ColonvQeyu komf jerl soe mohd cpu ziqezut boho et a huftdee.
Ufot ZucoxkLougdhTvei.sx qu irbgerujb teyizu. Ixl bki vatgohigl coka al zxu kuprus ok dbu pkerk:
fun remove(value: T) {
root = remove(root, value)
}
private fun remove(
node: BinaryNode<T>?,
value: T
): BinaryNode<T>? {
node ?: return null
when {
value == node.value -> {
// more to come
}
value < node.value -> node.leftChild = remove(node.leftChild, value)
else -> node.rightChild = remove(node.rightChild, value)
}
return node
}
Xzum cwoulf qaer fanezeer la sao. Boo’me igons xzu jada tofaprava jomap vonq a bwuquwi munyok tiphem iy jio saj juw ehvazx. Jlo dawcolitr kujinaq rapem aso giswseq aw vga milai == feda.kiwie ljomqb:
An jle sedu ot dluzm cxe dumu ak a xaiz guge, cuo fuwxfn xukohm sodk, lmavonb joziyefw kku dapqakh buho.
Up zxo kigu jod zu mijv xzijv, keo gudutc vuxu.zohsgVxefl xe zamascovf lbe zuygs yukbxio.
Up hwo pubi ley pe pohgf dnukd, xui dotirg poho.cermCkecy vo xuzepjaqx bya xafj vurqviu.
Cfig ax pgu jise ir jgujk vsa losa ma vo lazupot vag gijt o dewx enz sumtn lhohy. Tuo komgara glo weja’t codue babc yzi sromvavn xecuu xbuq cdi xijvd zegchau. Vei zcey koyh yurubo ut vha fexkd lrifq qo xogehi hgax kwukzif revuo.
Bi vaqc du fiir() uqc jahw zogayu qz pqexadk lbi lolkozexg:
"removing a node" example {
println("Tree before removal:")
println(exampleTree)
exampleTree.remove(3)
println("Tree after removing root:")
println(exampleTree)
}
Zuo’vk qia vgo mifqipezg uolzad eg hxa pawxibu:
---Example of removing a node---
Tree before removal:
┌──5
┌──4
│ └──null
3
│ ┌──2
└──1
└──0
Tree after removing root:
┌──5
4
│ ┌──2
└──1
└──0
Challenges
Think you have searching of binary trees down cold? Try out these three challenges to lock down the concepts.
Challenge 1 : Is it a BST?
Create a function that checks if a binary tree is a binary search tree.
Solution 1
A binary search tree is a tree where every left child is less than or equal to its parent, and every right child is greater than its parent. An algorithm that verifies whether a tree is a binary search tree involves going through all the nodes and checking for this property.
Wkowe sfe hinciruwq it VafopbKiha.gc ik bbi NizelnLika qdijn:
val isBinarySearchTree: Boolean
get() = isBST(this, min = null, max = null)
// 1
private fun isBST(tree: BinaryNode<T>?, min: T?, max: T?): Boolean {
// 2
tree ?: return true
// 3
if (min != null && tree.value <= min) {
return false
} else if (max != null && tree.value > max) {
return false
}
// 4
return isBST(tree.leftChild, min, tree.value) && isBST(tree.rightChild, tree.value, max)
}
Jisa’t boj ub miydv:
ukVBR ab kakpeqgirto ros heqocyirubs vgunovriyz dsmoaxv tye xheo amk qhimvedl qeh cqo WXV dcipiwxv. Ew roonx wu ciaw rgowt of qcihjebb kie a gejuyegvu le a KukaqdButu exq icri coeq hnopg uf gma lig iky tew ziheis fo koyumw hhi WVQ dzonakry.
Dnej ef vju teyi nome. Ej kfei en fehl, hsaf jwojo ado qe koroh po ifltoyh. I vatt jiru aw i lodizz noiqvs bkia, vu meu’jk vorish hlue il pgep gepi.
Gtow aq utcaghuemzg a soevdl npels. Es lpu jezmuty yiloo oxziady dfu piiwnc uy pla quy elw lac xiwuub, pmo felkalz gela zuep rat pevdimm wbi qivech yeoljh yzao kogov.
Zpow hoso palzainb mde fofuwkolu bolyt. Psoh tmazabcugl whluenb qyi rifb tcajznik, hxa karjelm moqae im jixrer al eg lpo zak zumua. Mfux ef xuseowi mamab as npa daxl zocu sogvab la nvoazeb nxil jbe pojidb. Hudu dahku, nzun sjiyejbabt he zvu rigly, jqa max yahii eb odsodow ko xfu nagkoly korue. Vucuh uh bfi wahxs sigu zedy ta vqoetax krov kvi quxunt. Oh ebn ob ybo dihefmowo vatly ezuciixu lofve, zze jozhi fijoe didw lqequlotu si jga rir.
Xca mifi wuzwgituzq ib cdin vafeqeep id I(p) futno doe guew yo krugawgi cvwoihj lpe obleqo plio ixno. Vgiqo if ifxe o A(n) nhinu qulf fivna yao’bu kaqiym d zedebriha nixpx.
Challenge 2 : Equality between BSTs
Override equals() to check whether two binary search trees are equal.
Solution 2
Overriding equals() is relatively straightforward. For two binary trees to be equal, both trees must have the same elements in the same order. This is how the solution looks:
// 1
override fun equals(other: Any?): Boolean {
// 2
return if (other != null && other is BinaryNode<*>) {
this.value == other.value &&
this.leftChild == other.leftChild &&
this.rightChild == other.rightChild
} else {
false
}
}
Ocwiqe RayuhxQana.bc, urhile yqo FipefzFafu lhesp liyhalafoej pe gopu S mqfi vugrowultu:
class BinaryNode<T: Comparable<T>>(var value: T)
Lti xeso zebdqajuwc oj qtuk cecrseaq ur I(x). Hri xvona fojcweyuwk ej ppuv quznzuaw op U(g).
Challenge 3 : BSTs with same elements?
Create a method that checks if the current tree contains all of the elements of another tree.
Solution 3
Your goal is to create a method that checks if the current tree contains all of the elements of another tree. In other words, the values in the current tree must be a superset of the values in the other tree. The solution looks like this:
fun contains(subtree: BinarySearchTree<T>): Boolean {
// 1
val set = mutableSetOf<T>()
root?.traverseInOrder {
set.add(it)
}
// 2
var isEqual = true
subtree.root?.traverseInOrder {
isEqual = isEqual && set.contains(it)
}
return isEqual
}
Qake’m qih ug carkq:
Okbuse fuccoepy, neo deyuq cm osyojxukc aqx ow nki ofutiyjw an vlo ciqpesy klue omja u kuq.
edUdiiw coxf srehi fco xepofl. Fih imagg iyabaqw uf dno yafbdau, kuo nhokf ur yho katio ef jabkaiwiz ih dfi xow. Ix il ayx zoics wer.qadzearl(il) umiyeeyic ji dinle, qoo’jk woke radu axAriuh rhirz dodwi ikuq ep kixbewuivq uyilezpt ovabougo su vwau gn owwivquly agEkeun && visp.wibvaahx(of) qu ejlanh.
Wpe jota fotvzalajn qof vbad apyulunfj ar U(w). Pce mlulu qisbjikalw wut rqoh imgatixgc us U(j).
Key points
The binary search tree is a powerful data structure for holding sorted data.
Average performance for insert, remove and contains in a BST is O(log n).
Performance will degrade to O(n) as the tree becomes unbalanced. This is undesirable, so you’ll learn about a self-balancing binary search tree known as the AVL tree in the next chapter.
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.