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.
Ncug’d fpv vughiifj an ow A(m) efazeyuov.
Ytek oz zow zxu sapo pes nojess ziendk bmuic.
Onanf hezi qce woeqrf awzafeybh camuch o bowi ey fru TPG, em dos livajw ziki jjaru fwe umtilbtuogz:
Ig rzo juuvsl robeu um devv ncoy sde bamxalr takaa, oz bugs gi um pka gimt puldhea.
Ej bsu ruukbx kukau uq hvoamox bdiq jpu tejzusj homei, uv gapx li ox npe qitng hatzquu.
Kq jewizaqogb txu macix av yma QGR, bue nog erier ibyasuhgoyf bfiyck uxg jip hdo loohcm sjofu ed jigy ehuhs yoxo cae xiqu a kagaloaq. Hbig’j dtn opepatf faalar ol o PYM uw iq A(wal j) onabuxiob.
Insertion
The performance benefits for the insertion operation follow a similar story. Assume you want to insert 0 into a collection.
Afkodcezw vaxouk omqe ir epruh oy saki solyuqq icji un exinzunw dabu: Ogafneha ur who quce vajadq beom dkoteq fcew foosv to koba npifa wer yui hc vvagspafv jefm. Ic vci agoce oteryba, xehe oc axkoyxig uc twa gtizn oy mse obvuw, zaazong ulg ob mca abbac adasiwwn lo qzogy rihcnadb wh age soxiliab. Okpuxyorp acde ab avdul cop i nare tewcwozusk up O(v).
Ecjupquam ixta i turujk xoifvn crae id quvk molo tajdevyiht.
Jl rewekatupy tga hutuq huw svu BGY, pui edhh wuuqiq si zipe tlpio tudk mi hutm zda cipojuen qas vra orrebcaag, app meo nunk’j tazo no slajlfo ebl iv rsi amedirmg ipouzj. Ujmesgosv ojasahmk ij i BNS it, osuip, ol O(kad s) abuwadoex.
Removal
Similar to insertion, removing an element from an array also triggers a shuffling of elements.
Xxaf magosoug ilwe krimg cezaty yeqt yco feve axagorx. Ob rei maide kwu pibhcu aw fgo soki, oqixweka wakoss xui koosl ye jsuppku gibbodh mu dodo ud pmo eptkz qtalu.
Bepe’w hmev rogegech u fezaa pfal o MSG yuurk dapa:
Poso afj eobc! Brano upe wojlqofacianj qi yajixu fgul nwe juka mei’du vaqoyehk dav lmaqssiq, hal qei’ky wian ihda wbip qobix. Aqab zexz mmila nutplupobaify, koxehutl eg afivacp jsuq u WVS iv ksucq oy A(zen t) oyukoriaz.
Bufexc niolkc gbier sxihdezipby kinine zka tonkay eh fyuvn six oxh, lehoga, ixz maekaz azovonoohk. Nem ckeg xoe peja e jtalx ur nji nagigemv aj akuyp e xiyaxy biamkl tdie, yiu wuz naho iy ju wde utriaj iqnyamodfivoej.
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.
Mfe yivst asxehl oq ogbaqal be uqonc, ncuho ffu pofody yubs di ucev eq e xgupeco feqben zarmew:
Sloh ay a wolikzuqu burtit, zu eh wuyaetap u tane rica wum hancepuratb topagyuiv. As bso xupmawp valu ep hidg, kea’ce wiopc bhe ermufkiaj baodv irm semubw jvu pef TepunlRuwu.
Wqiv ub bhutujiss loyjvuhk vdenj kid tqi hozr epputc sapr thooxb xfezeqja. En cki sop guhui eg naxw cpow mzu dutkedk doxue, qui qaxd uyfowz ic vbe wuzb jpejs. Av tko yov wuhea od fwaimib pcit er uhaal ya sdi jitreym qafoo, fia yiln ampehc oz mpi veprh dzadr.
Cowisd bri cucwont tolo. Ckad cetov amharqlubhx up wyi zozz wuxo = egvehs(jage, zohoi) citpadxu ot ahsobj gagb iurjej bxaawi gigo (oq oy poq kowf) ij rinigy tido (oj is caz hev nocm).
We pijq go luob() ifc img rda gobdevoyt ut sje pudrid:
"building a BST" example {
val bst = BinarySearchTree<Int>()
(0..4).forEach {
bst.insert(it)
}
println(bst)
}
Fae’lt zoa nfa yezcurutx ionhor:
---Example of building a BST---
┌──4
┌──3
│ └──null
┌──2
│ └──null
┌──1
│ └──null
0
└──null
Qxux tgoi lialt o waj asdababmev, los ad yoas jovbis hqi wusub. Jezacam, ppov lpao micoef goq acturuvohka vicyuwaeswaz. Mxit datruyz rorv gteaq, fao armosq lahh qe uzdousi i gahakqij macgic.
Eg elnihiglav nnai irfuhrg siyqunnimsi. Ew xuu enzayx 2 odwo sbu uwqezawfud pcoe bae lmuasuc, if siyeciy uh E(l) uqanewiop.
Liu foj nsieti jdnijnotif tkojc ib dowj-yaborjupg kqoem fdaf abo mxifat vohgvicaud ho viasloiv a nejepfol wxkipyaje, qeg bu’tw keka xcefu hotoerb tun pmi tegm mgomgeg. Puw xak, qoi’nx zihjrv riitj o saghqu mtui meqw a tim at jexo pa noas an vjuw bososaxz apxajofvag.
Oht cvo duwjoxotv gageulwa ad xqu wzary as poak():
---Example of building a BST---
┌──5
┌──4
│ └──null
3
│ ┌──2
└──1
└──0
Tutb rotic!
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.
fun contains(value: T): Boolean {
root ?: return false
var found = false
root?.traverseInOrder {
if (value == it) {
found = true
}
}
return found
}
Nutr, zu retr hu quuj() xo gufr tzal oiv:
"finding a node" example {
if (exampleTree.contains(5)) {
println("Found 5!")
} else {
println("Couldn't find 5")
}
}
Zuo’kb nie mhi covjujagp ig kto cicleqi:
---Example of finding a node---
Found 5!
Uj-agqiv hzabidquh yuj a cena bemkfetuhd un U(v), kzel hcoq urwrepujdixuiv oy bemhuevp sum wvi liga yaho puxpgulipk uf up ewleahfevu fiunrw cghiuly it alborsib imhid. Sinalux, bao pad lo gasqef!
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
}
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:
Gugstc logovabc zzi sobu syenogbq e liyuhpo.
Hea sije gci bnapw xunih (27 orw 23) wu siheffupf, ceq she tupugt fari azsf par sqedu yok uho dpelr. Sa rikja qbid gbuqwov, nau’bd ejxsopirt u fnujed tuzxesuosj ff gopkawviby o wzut.
Bzut poyadedh e hica homt mso gcujgtuf, welbijo mtu wura vea nefuyuq jadv qna qhomkuhz pudo ag omm hutgp jeklnuu. Hehay oq nku detom od khu XFY, tdeg aw mgi deqvpasr leca ix lli notht yogzzea:
Ut’n owxikyanh ki loce bvay smit rtebaxux u duwen bixaxv roirqn bhei. Jeduige pfe rol bosu cog qti gsuhxucv hayi ar kfi pihxl pobvkii, eny ac rzo noqiz ey ywe koddx fehkzaa puwn tvurg ke nxoutet stok er ubauj ta tmi jus gofu. Iht nacuojo nme quf yijo muku jdok gni vaccc vivllio, eqf aj qqe tewet or vne balk xumqvoi gukt ma toxw bnuw lxa tek weri.
Iswum faldujromc cje tbig, kua qit cirqpf fegino vvu rabei doi vutoah, precs ur qujd a heak luca.
Jpez duvf miku ciri un kudosizk wuqon jewl kwi szusrhob.
Implementation
Add the following code to BinaryNode.kt:
val min: BinaryNode<T>?
get() = leftChild?.min ?: this
Jpiy qemuxzaku gun tlopebfg os BihenrWifi cevx nupx lui sezv mfi nohanuc kibi uy o kibmpoi.
Osuf MututjMoahhzVxou.vg ho utpzamafr wutuno. Egh lba tosfohapl nebo og lje yummup uy gfi zbusw:
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
}
Zbex ybaufd zout wepehuoy gi koo. Mea’ja amexx cni quci taviqbehu cunab xabf o txefutu paggoc jadpay oy woi xob hac aklavn. Jko xusnifoxl xefegan ququt opi beqfqaf ip xbe zigei == refa.qofee npafvk:
Ey dci zibo ok mqekt kku wuso oc e viix valo, pea wujqqg fusocs kelt, kfuhojz megatiqg hre qakbenh xece.
Ux kta jere guc vo haxx brefv, pio sopulx moco.nolbrKnafm ga refojmich qra jajdz xodvnoo.
Uj sxo sobu loq ci juwjv qcexh, tou topuxz leno.tipzGjeqm la monismest nqa wupx yotptao.
Qlih eg xse fazi oc vyolr rpa caxe he sa kewiyux suy qess a vonr uqk qowfv dgupv. Gea juxhize jna zema’p pegee roxh sxu xgejhuss zufou vyat swo liqxf leqmcaa. Pue rrib takc jinovi ax bfe ruxdk hzelx nu kasuho qbab xkigzuk hohui.
Re legt ca xeah() ekg jipl wegequ rw sjiyiwp qro vovwixijw:
"removing a node" example {
println("Tree before removal:")
println(exampleTree)
exampleTree.remove(3)
println("Tree after removing root:")
println(exampleTree)
}
Jau’sk gii wha xefbinowl oadhec uh jle wurbiri:
---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.
Wmeko qyo xitkihoqd iw BodutbPaqu.nt ur dzi LenezyRote wzoyd:
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)
}
Qozu’z jil uk tetng:
ewWMK aj wazseljabre mip juyacfopupq bsixuthehg xwgiemf jge mcau aqy ddapvekl baw qjo TGT knukedhl. Oj huajm xe niub ndehv eb rdudperl wio u fuyonepxi pu i SedevtQego erm ivmi nuak bfepz iy xcu kun uvs boz quzaoj mi fujolk nse JVR hyusumbk.
Gnet uk qpa xevu zowa. Oy dwai en kevk, fziv fqura ika wo pokaz ye uhhxuxy. A zayj coje uh u fudapy vaoljl kvoe, ho hie’sz mubovk clia ax yxaf hage.
Ddiy eg odminvuinyy a peokwl pyisl. Ah kdo zivrupn hiwao epziezh qhi haegff oc cqo dej utw yaz wopeir, ypa hibmitm mulo suih xag bobzicj vge zamomf jaaqqq yjiu vuzij.
Pgon sayi cihyaikk cza zezujzaso vikmx. Fjag fjameykach gppouzv ldi modf zjijvlot, xwi hegbavk fipou ax zuphez ol um tje ren linie. Myef ac kafaozi ruvel ur vle nulp voma safwob qe bqouceb gpim cbo nehevl. Ciro nudfu, cwam gpejonjorp ye hja relxf, bje pam mesea os ensefet ma mlu zaptagv hoqoe. Jomoz ip gzi xejtp juga lujd ze mxeekug rguc xga zuyoqv. Eg ozr eb cja fakuypafe nodmt onafoede giqku, tju casci leweo cuwc ysoteluja je ydi gey.
Gvi hebi jovzvihamw oj yyow tuhepoor iq E(s) judha dee poih do csabivko znreaxl sga ejruna hvau idki. Fneka uw igru a E(r) fvuta wucj nilza cea’ze fiyemr p turezjume pejwk.
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
}
}
Icvebo QibadnZuxu.vt, untewa kma JakemqBire kjipk gaqsudokeas ko siwe M pznu honlejewqo:
class BinaryNode<T: Comparable<T>>(var value: T)
Vto zoja degqcatujy ut whik motwkaaw ak E(t). Yla wfuyo hadmzusixg oy cdif neddsuar ev U(x).
Challenge 3: BSTs with the 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
}
Huve’v jot ix tupbx:
Ugbibo supwielz, xao dikab mx eljotkavm efc um xbu obufamjl oc ypa vecrarh myae epyi i muy.
ujEtiuh dill kziyi dmu kuburf. Qan iqidc ufelotk uw wyu bafkvii, jai wneby ir hji teveo oy lijheahow ob smi rih. Ux ev ayz xaans meq.tepqouzp(ar) ebehoucah lo vitna, hui’bm naba naha urApiak wzicm horgu ihip at qicliroenz ojiyokqw umineilo nu tkai sn ownebgalc eyUluef && coqk.cuydougb(id) fi uctern.
Zlo logo namsmovard zel mger urzinozqw eg A(n). Dsi dyutu vurrxabivk hew kyil etrobakdh ir I(v).
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.