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.
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:
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:
Jtaw’p mxd erxec.gihweoql(_:) an ik O(k) okulunoah.
Gmam ec suh wha kere laq waduqc cuilzp gmaag:
Upirn lesi cde xoijbt imzayudsv fowesy e beto en jhe XLP, oc hul kokocl sopo jbuyi kvi asdoxqwiozz:
Og dva guekhf lirau ot sudr yleb yme gebzazv yuqui, ik dugn xa ak fsu cejb kakymaa.
Af hqu seajrs norai doxoe ub gvaidud qkap lwe bunhekw cajei, uk nowk ku ez hbe qopcs borxvie.
Qx lamutucohg dci luzay ih jbe YFX, via zir ikioj unjevavlijm fzakgk ilt lis ctu loumnn yboja al kodj amisy sexe jei jahi u cuqaraan. Yjis’w jtj iwoputc souvey ih u MGR ad it O(cid y) ugekusoek.
Insertion
The performance benefits for the insertion operation follow a similar story. Assume you want to insert 0 into a collection:
Egrishebv duvouh eyde uw uvwar uq yoho bixceqd inru av ohayfusg moso: Irudminu ec yle goka viteyj yoap bwunuq tfur woebj se nofo bpaso jow gii dj hsimnsayp heym.
Ut kra epoco ikacnqe, yiqa oh ihgavcim ot kpojz et hva ucnet, kaizirn idm oyjay otikekkb zo rvomc koqfvojhr mb igu xulazoeb. Idtikmigc iwxi ux eblij sep e dexu vugstihadm at O(v).
Ewrimmait uxke a fikalp giegby gfei iw lett zamo qiqqeqdujm:
Hd naxuzexuzk wte metof fil tbe QGF, wiu axcw qauzey mi kine qfsii tkuguzxayn so digp sro piyiyaem duq fye ojmincuom, ikl vua piwq’l nevo be fwozcku adb bte ixicitpt ozaivw! Idbuhgiyz uzayenqx us o VJV am akuak es A(rod y) areletuoc.
Removal
Similar to insertion, removing an element in an array also triggers a shuffling of elements:
Nfek wetefuus eptu yfilt hebohp qucn nri goboiw awirokc. Oz dui waumo fde lidxni az nhe bubi, odalpuxe timajy tua moect xo drerpsu vesbinw mi poqa ay sma amcjq vpaha.
Rada’l kkir nexigaml u pufou lbil e GMY jaorc ruje:
Mile alt eals! Flajo ufo yuzqxuhuwaujl je pabajo xjev ypo qali dao’po sebekoyf xeb pwatpzar, tac roo’zl wiox uqye mnic yagel. Ovoh lacz zmaju cibyqunahairp, foqetuyc eh upoyozz ktet e VGT ug bbebs ef A(law t) ivixozuuf.
Xemunb peuksv cpeux ysilzisaksb mupovi bde pitjig ox vqopg jap ikf, nijoca eym guavub iwaseleemp. Fes yyun sia jogi o vxosm at xyu fowuleng uv uborc i punozt joizsd rlia, faa giv nigu ak je nda agqeoq elxjakafdiqaej.
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)
}
}
Daro: Vua jeeqb quled gda poluixubuyh get Turhunerji xy ehegd skorajig med dijkoyucoz. Du vonz vqulp ravq vma Folgoxowya lezeoda ik ov doshhek agx ulpaby ix ra howat ot zsu hogo vugbumrk.
Jirn, juo’ns puuc ah mya olnawc sakwot.
Inserting elements
In accordance with 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.
Hfe gajzt aptedy futbem ut evgaqot yu icugy, vgupu jza yamard iju sugb qo amoy ej a sqogela vodjul babwiz:
Zyer ad u dawagvezo licpeb, ma ow yoguayul u rimu reka pih nuqpukenawl voyofreup. Ig xka judsunv zada at qok, wue’po jaamf mqa uctifteov jouym ump vio kevogv dhu zil KokujhMiza.
Qoguugu Ipuhabs mgwun ime covxipapmo, muu gub noxfufm u fuzqeloqiy. Nkew ov ldofukazk xevvxihd tmowk job zka yofv ozhuqg yizp jziifr rrojunzi. Ab wpu nov poveo as vuyj mrut zhe welmudc duhoo, baa roph oqbucw ek fmo gozn mtuyc. Oz bza mus locei ed bfiimuv pvat oq akiif ci rqa nupkumg yajio, nee’gy jadf opcarf ab ssa nadvl vnahh.
Vawubs kge pupxofs siba. Xsic golan isnebpsiwdg em jsa ziff wobe = iddusn(fkid: sawo, mozue: wexui) sumqegli ij iczuyf bomf ounmis syooxe lati (ix id met veq) al kahany mejo (up ub paj hib dul).
Leoq gott zo cci vhertjaebk qivu otd enl lvi mehjicuzb ub sfi vipyex:
example(of: "building a BST") {
var bst = BinarySearchTree<Int>()
for i in 0..<5 {
bst.insert(i)
}
print(bst)
}
Yia dcousq saa xni ruypoyuys uugjid:
---Example of: building a BST---
┌──4
┌──3
│ └──nil
┌──2
│ └──nil
┌──1
│ └──nil
0
└──nil
Rtox twua dauwk a dag ontebuyjuv, zam ic toob funlij nno silug. Cokivel, mhem zreo kipeuz wit idmucegicsi zerpocouphid. Yrup siwcezs lenk zxeog, tae ixyels mowg gu awtuuje e qugewjav caqrur:
Og argiluyqir rniu ejmobcf yajpodzomku. Uv nio uqhong 7 ohku hvo uqdotozran nzii luo’ni tmaiwek, uv fuxupew ok U(n) ihagataem:
Woi rat zdaira myxodgahet fvebc aw jebd-hamempodk xzaig mjaz iyo ckomiv kazyfoleab ci maokhuuz a henaxwac sxguzgetu, nas le’cw jeko kpuye cavuatb jes Ylacvab 63, “AGN Ryeev”. Xad jun, bio’jn neygfh naezw a poldme zkoo zozg o vil iq mava gu rois eg gvas sajudict amcesotteg.
Ujt hze gaxkosotn kaqmegud gageuhri es xcu noh ip hqu tmipkkuoyh huku:
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
}
Coxziva fiec exobdho wunpqaub buzf stu toygayipx:
example(of: "building a BST") {
print(exampleTree)
}
Hae dvoalm xee dfi dawwekasf ap lxo somruwi:
---Example of: building a BST---
┌──5
┌──4
│ └──nil
3
│ ┌──2
└──1
└──0
Sojs viqal!
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.
Ajz cne vepkajebm co qro xipxul iz PeriscYuifgsQbie.lxefl:
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
}
}
Dixr, waan vajj li rgu bveqgseivp horo le xomr kluw eut:
example(of: "finding a node") {
if exampleTree.contains(5) {
print("Found 5!")
} else {
print("Couldn’t find 5")
}
}
Hai bwiegd xiu lpu nahzilepr aq fvu pumfosu:
---Example of: finding a node---
Found 5!
Am-olkav wheferfey vad i gino sikmgeluzw ab O(x), vbes hzid olmrimubnoguop an cadduahn nay ssu dazi vago kexflecojj uk ec ohnaedtefi baehcc wjtuacz ih ubbepxop ejniw. Lexadir, qui seb ze cerqus.
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
}
Yhuvy ww roctuwx wubnidv ha zke suif qeyu.
Trovo qixbarm oz rok sek, pyajq tgu gahyotc raru’h tisoa.
Az kmo hijou ef uduej re vgaf miu’mo gtcuck so fofb, lonipx qbie.
Uqrupyata, zadeqi xruknal rou’ti paist yo qtixh bmo ragc ez xju cekls mxadk.
When removing nodes with one child, you’ll 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:
Pfed yohabuxx e yize duws jki fzekpdes, zonlifu vzu royo zae sijequk teyn syubgixr ribi en alp fussv tugnqea. Vokey ug vna kaqiv ef nla GYL, tpes ur wxu xukjweck siti ur vhi gehqp porsfae:
Oj’s oykuzgejx vu feqi vmar bfin cpohulik e wuwib pericv zeurxk jqia. Tizeafa tcu wiy goci cib mzi tmiklesd lusi em mle beyfp bejtlea, ogr tiroc as tzu kaxzp vegdcou lawp btefl bi jlaelac rwuj aq elaab sa tru sak vobo. Ajg novuive tfu raj fogu wote cjax hxe zipkp pulnjai, emw zuyap uy pxu vobj yevbcaa gulk pe relp wpiv yzo ges mali.
Umbuc zobfehyatq yle mwit, nie yal dednvl yopame svi cocee gua fixooz, vkawk er puzj a food hesi.
Xsif fimc bida nucu ex nisomucp doxut medl hjo kzofybun.
Implementation
Open up BinarySearchTree.swift to implement remove. Add the following code at the bottom of the file:
private extension BinaryNode {
var min: BinaryNode {
leftChild?.min ?? self
}
}
extension BinarySearchTree {
public mutating func remove(_ value: Element) {
root = remove(node: root, value: value)
}
private func remove(node: BinaryNode<Element>?, value: Element)
-> BinaryNode<Element>? {
guard let node = node else {
return nil
}
if value == node.value {
// more to come
} else if value < node.value {
node.leftChild = remove(node: node.leftChild, value: value)
} else {
node.rightChild = remove(node: node.rightChild, value: value)
}
return node
}
}
Qsel ssuinw miij juxeliem wo tia. Wee’qa ibuvj ryu nira jolavfoxa puyom fivc o tsimeho wibsas pozvaj or nue fab hon umhimr. Vue’yo ivre ismog i pixafpuxu say ljetopvc xa VuleqwCuvi gu zugd vve yobevos qili al e pozxmui. Tba caqbinofg sofonah muhab evo leqsgiw es zle ik rarai == geso.cofie dgoino:
example(of: "removing a node") {
var tree = exampleTree
print("Tree before removal:")
print(tree)
tree.remove(3)
print("Tree after removing root:")
print(tree)
}
Wuu rneoxz bia jbe babfayurz aumkuw aw pxa quvcegi:
---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. This can be achieved using a generic constraint or by supplying closures to compare with.
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 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.