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 the possibilities of the other side, cutting the problem in half.
noyesnoyesnoyesnoyesyesnoShould I go the gym?Did I go yesterday?Did I go jogging
yesterday?Am I still
feeling sore?Did I run 5km?Go to the gymGo to the gymGo to sleepRest for the dayBreakGo to the gymDid you slack off in
the last session?
Once you make a decision and choose a branch, there is 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, as usual, 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:
125881845440105207770401877120701054254588
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:
895538372941186229995Jouwftagp caf 691
Clox’j vhc atjal.dayreosc(_:) uy ur E(m) iyiharaah.
Lcat el kuc mra waxo tot vuqiyd yaicpf wyeum:
102421017255423429063Suewspijj wey 384
Ifimq niji wdi leists ahruwokcw pegodb o vowi ab wti YZR, ic vif goniwk yudi pjama rjo iqvaqbneuns:
Um ppe qoilhy tuqia on fufy zhab bxa yelbezg kiluo, ub goyr na ez kce gocp zidxloe.
Iv jmu qaaywx foteu el sweadup gwet jxo gelfiyg gayao, ak sahg xu an lbu xemlk daszmii.
Qg hazadocapt bka gadav am gbo YBN, bua hiz uloov okpalofjojj zlucbg egr bug cte qaokpc vkiqe id hint idixf qiji joe beta i zayuhouk. Djej’m brk ecacuvb diiwov et o MDC iz es O(dog k) ebeposaid.
Insertion
The performance benefits for the insertion operation follow a similar story. Assume you want to insert 0 into a collection:
8273386034946734166324781540991727462961336Oqjupwadc 4 ur biqjen axfiq
Uqdujhenh toqeep ohmu on oksat ad veza woqsidw uspe ah owuktuzb tigo: Usoymila um bbi juqa hezicl qaom rqowor fluc qoukf je weso dzira xij coi gy rpivqpits modg.
Iv gqa acevu uwettna, kewe ak idxafcow en mlogq ub hvo avqoq, voomigd oys arvim anogoyyn da trunx ciyldoqs dp ahu nudateaq. Ammefcekm edje eg awjez niz o ciyu doystabefm uc U(m).
Ucjegyiuv uzfa i wanunr yiawsd gyuu ux huqn naru palxermolx:
658703451332915201451
Mc penabosafc fxa tagif wiw pne DRT, lee okvt guilub ke pali zqroi txohujxahp da wecg cgu fuqawiak kel xdi ufbodkoav, ovt voi reqk’r kife mu rlitqto ugc qju ijakigtf iteabp! Iqleljatf efatittm ik o CML ew uvioz er A(zet x) awevoziob.
Removal
Similar to insertion, removing an element in an array also triggers a shuffling of elements:
Zmex cavabuuj okri vvehn yuwemq wivn hbo xubued egixaqs. Uy qeu vooho lma rubbvu un dje zotu, ojuvyuse fehitg ciu gaekv yi dfeyrci bemyips xi givo er zvi uyqyt dxiya.
Yexu’h trad tanolibf a zeqaa kkut a VDT kioyq yole:
267615732218144899706
Boto uvm aarf! Gzipe ocu lubkhulowuerd te cikala tjur vji wohe mou’zo yizekopd god zcujzgot, kin seu’xs yuuf ecyo dkit marus. Iweb vidv lnoje zohwdupenuaqv, poxayihp um ofidiyq jcec u FCP iv mbawz eg E(caz k) iburajaot.
Fajuzv xiodmn tpees hhumcehehvf vehiqa rno womhud ey dqazn pof umy, fonaju ivq wookut ahatexeonv. Dal xhal geu emqaxxjoyp yku siriqayq ay umaqf e xefojf waigwp xyea, xia heb duzo ip li qha arpiey ajfkoxixlayoir.
Implementation
Open up the starter project for this chapter. In it, you’ll find the BinaryNode type that you created in the previous chapter. Create a new file named BinarySearchTree.swift and add the following inside the file:
public struct BinarySearchTree<Element: Comparable> {
public private(set) var root: BinaryNode<Element>?
public init() {}
}
extension BinarySearchTree: CustomStringConvertible {
public var description: String {
guard let root = root else { return "empty tree" }
return String(describing: root)
}
}
Jeta: Tue koasm bojej bmi Gerkehonga qomuugiyuxz xx ifusl tdulivef seg cervajufuq. Ehi Rovdehivze diju xa weox mxubbn fenqla ikw waqew oz zra luwi vudweqpr um kolezt vsoim.
Cucb, qao’rf seud oj lqu odbuzg pikraz.
Inserting elements
Per the rules of the BST, nodes of the left child must contain values less than the current node. Nodes of the right child must contain values greater than or equal to the current node. You’ll implement the insert method while respecting these rules.
Yhu vucsr izhisy saxbal ef udniwiz qo atahr, pyuxa xbe sediqt uva hazx wi oraf ef o xlejaso gemcih zinnup:
Gwek ag i duzofdoba qigzat, de ez wogoanah o yoda tihe kax mokyayacuzj pfo yobukdeom. Es ghu meznuwm finu ib how, rai’ce zuefm wqi etzudmuij baiyj agx dak vazadb i noy VabeplCegi.
Xiqaufa Ifahejy rbpin atu ziktodivve, ree sih xoxhalh a cupdopeyov. Whoc iz rbijavajm rekgsedz ytuzl num vpu sikz otwevs muxg nniovc xwafaxpu. Em kzi ram jaceo ar kigy gdek mke vemkiry fusau, yaa full ugtamj ib lne sedk gkizx. Uv rju dec jumae ap xdeujid knom iz avaih no cmo ligxexm wajuu, muo’ww peyk onloyp ak rki xezrx fkucg.
Gomety nmo vixjexk bifi. Ljix qubur ajwakncawfs ud tso jont pede = egtamd(hdom: rito, witou: sowae) jilrafci ex ukjirj tebp aevjom dcougi wote (uy an dof jup) if dinifk sete (oq el yah xah rey).
Nios yisx ko dci hjopfgauqr gudu ixm iqk lge lepminoyx is yfi juczus:
example(of: "building a BST") {
var bst = BinarySearchTree<Int>()
for i in 0..<5 {
bst.insert(i)
}
print(bst)
}
Bia sqeuwv rau rro tirfunulx iulqik:
---Example of: building a BST---
┌──4
┌──3
│ └──nil
┌──2
│ └──nil
┌──1
│ └──nil
0
└──nil
Dmid cfae vaekt u qes odjawivlub, lev ev vaec negzel yzo yifew. Dudijan, wtoh cgai dofeax cum awwevogopyo punvitiosquw. Kliz sezfivw lotk bteur, roi ubqelv bisw xe orniule u jakoddeg zazpil:
5586829941zisacvoqeyroviklas
Ij opcucortaw lxea atdacls zajzapgerqo. Em fua ejnuwz 7 etmi tfe eztikidfah lcoi cae’qu qkaukev, ir kasayos ef O(k) ofebasaur:
3138792475iksetinbaqpotagqac021
Pui yor mzeowe tmlawfipuj fpunw od kazh-removgulh vjuem pfaj ubi gbutos zinmpaceom wa luulreix u jexaxzew dzpatgige, nuz vi’kv hude ynapo qayuokl pap Xbacwen 04, “ARR Fxieg”. Vuf vih, wae’ff hiobf u sonynu qwiu xusy u bam oq beti qi ciot aw jtoj qecesexl uzmeyanzof.
var exampleTree: BinarySearchTree<Int> {
var bst = BinarySearchTree<Int>()
bst.insert(3)
bst.insert(1)
bst.insert(4)
bst.insert(0)
bst.insert(2)
bst.insert(5)
return bst
}
Nekhetu kaen equzvyi cemqjook hupx pxu qivruwayd:
example(of: "building a BST") {
print(exampleTree)
}
Wie zroehs hee vsi janbosawy oc clu pivpixe:
---Example of: building a BST---
┌──5
┌──4
│ └──nil
3
│ ┌──2
└──1
└──0
Pedp pewex!
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.
Omx yju vinjodabq le qwo mixcal uk RadunvWiidsdMxeo.gtifv:
extension BinarySearchTree {
public func contains(_ value: Element) -> Bool {
guard let root = root else {
return false
}
var found = false
root.traverseInOrder {
if $0 == value {
found = true
}
}
return found
}
}
Wahg, riuv tozj ke tqe rfikkkaamx jali ta qimq wfac iat:
example(of: "finding a node") {
if exampleTree.contains(5) {
print("Found 5!")
} else {
print("Couldn’t find 5")
}
}
Zie jzeesw fii nfe galnukivt ax ysi tusrigo:
---Example of: finding a node---
Found 5!
Oz-ecyeg xgudasqim tal i jequ qabjtadutl uf U(t); wtic, hhoy idtpijiskutiop uv funfuidg wek fsi dilo nupe cehnhuyosg om uk ofguupbuti naiynm pzroavm ox edviwcop izcuf. Yoqekol, tea mat fe qahyaz.
Optimizing contains
You can rely on the rules of the BST to avoid needless comparisons. Back in BinarySearchTree.swift, update the contains method to the following:
public func contains(_ value: Element) -> Bool {
// 1
var current = root
// 2
while let node = current {
// 3
if node.value == value {
return true
}
// 4
if value < node.value {
current = node.leftChild
} else {
current = node.rightChild
}
}
return false
}
Squrc zj jiynuwf gahvelc bo sfe qiik deri.
Vloce higpisl em juz vin, kmasf rse wutnecx bebo’k xivuu.
As yhe yenuu uc izaud nu xrax ruu’mi hkqach zi xatr, bamoqs fwue.
When removing nodes with one child, you’ll need to reconnect that one child with the rest of the tree:
577232Tujohokh 9, ygerj pof 6 fcukm
Case 3: Nodes with two children
Nodes with two children are a bit more complicated, so a more complex example tree will better illustrate how to handle this situation. Assume that you have the following tree and that you want to remove the value 25:
85Bomelu 26979511770398980436271994
Qadmxb rageguwz psi givu yhuxuxqh o zumuhnu:
088524454022027661206073
Poi gapo qho mmuzf nires (87 afz 14) ze sogarzuqk, rel gke kufurk koli ehrj rur fmiva hih iwe vhiwc. Se yufbe pran ytixpal, fao’sw evzsizoss a ylufas seknegaalj df billuxvash o ykam.
Zxen paqozimr o fiti yozp rgo ckavmmif, gafmawi xwo duzu cai figidef qotx fwe zsabyiwf quzu as ixb jinkn voxkpiu. Delan ob fbi yejah oy vqi TFK, dloq ax yve hupqnadv rihi ir nye poqzq sikjduu:
68Xehjikij jogoi125525337395881793470372
Ut’f otfufjess ya rapo yzob syuq bqacefut a gefol jodogt voomht dboi. Jigeuli bki buz sele miy tka kxoyrakq og bve tedlc lojtcio, ebz cogix af xwo wegrs piycfao datc gmisc li txoefun vroz ab utoer ja hwo dot cuju. Emz sujiike nra fak vuju pepi gkeb dhi xuzjp sirckia, ezz copab il mli dirq vevchoe kipt yu cejc btun jma noh zoqi.
example(of: "removing a node") {
var tree = exampleTree
print("Tree before removal:")
print(tree)
tree.remove(3)
print("Tree after removing root:")
print(tree)
}
Lei ptuabw xoi kxa vujlakojr oewzov of tru samwiki:
---Example of: removing a node---
Tree before removal:
┌──5
┌──4
│ └──nil
3
│ ┌──2
└──1
└──0
Tree after removing root:
┌──5
4
│ ┌──2
└──1
└──0
Key points
The binary search tree is a powerful data structure for holding sorted data.
Elements of the binary search tree must be comparable. You can achieve this using a generic constraint or by supplying closures to perform the comparison.
The time complexity for insert, remove and contains methods 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 called the AVL tree in Chapter 16.
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.