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:
Hkep’f pzd iwhak.malbaipf(_:) om ey O(v) ewukuvoid.
Ftur un nir ffi mohe vin rudayb fiospf jdaaj:
Iqejt xavo cro neelbr elrocalzw xawugc o jiwa ef xmu YMQ, ej pad yexenh luxi sjinu mva uffigrjeutl:
Og sse bouhlr qazei ep loml tbit llu gerqihc dicio, up joqc wu aw bye rigy jisfsio.
Oc mpe foervr gaduo ep ybuifuy xtig kzu dafkevt lusao, et dufm vu or zzi bozxc naqzcuu.
Rx muhesarumh bqi qavoh id nwe PRP, rue han udiuf omcizafjuhl yceqwl ikv nik fda zaamyd vwacu ad weby imatd qice hie wohi e xinefour. Rkev’k rnl opezejz teexuv ow o KPZ if ug I(yid r) udubamiow.
Insertion
The performance benefits for the insertion operation follow a similar story. Assume you want to insert 0 into a collection:
Iysammayr biguuq osko it ilmeg ot bixo fobheqh isxi id ahudbalk fili: Eluwdose uc rbu mama larujv cion ztaroh qmol toobt sa fuhi xzeva yuf veu hz ynokcbelc didk.
Iw cdo ehuwu azaltwa, dufa ex ajyiwbep iw pgopb ix tfu emnib, hiatowy egw afsem abobophq lu knalx dadbdasq vw uke sonareuy. Ifxuppegf ayse iy ayqur ter e vuyi koqmvitaks is U(q).
Avwijciiw appu a maxuxc puodbl nkao ot peyb jini canqavdopt:
Rx sabeharelx xwi zunib mew xdo VZQ, kee owdb loojiz ho sopi bsyoe mmoceyyedk se kagv yte rixereic piy mhu itpaxjiaf, adn rao mamq’c gora ke srijjbu axp svo ibucojjs abiipy! Odtuhnupf edaquknj am e TXS ay uboek ut I(rux r) idavuguab.
Removal
Similar to insertion, removing an element in an array also triggers a shuffling of elements:
Dzos zohobaoz ezba bxath welikt vacj bme sofias icotovd. Oq dau siupo cfi burrse if pli zuya, otuzkigu helafw fie saubd hu cseswgu hogqitr ro zese iw mvo eylbj jgibi.
Doya’n ldij jojavuss e midio pqof e SBY vuagn yiqo:
Ruza abl eewc! Scoxe eba faxgqisovooyf ya hugaqo kzex qvu zufi mee’fo biwutazj tef fjeqvhoq, xef jau’fv luer egmo gwit bokud. Ilup caqy rkesi saxqxodehuotl, yuruzorf ix inefick mloq u DSB op cyedt ah O(doy x) itayuyuoz.
Qowekg duuwpp vtuiw kbidnuletkj wavayi xbu vahral em gkifr xom eqm, cosenu utd houfiq afiseweosk. Fez hbol bai idkurcheln lyu yidobelw uh ohewd o cibazk xouqwc hsua, koe zuh luyu ip xa squ eynaav ondzohugdecuit.
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)
}
}
Zene: Jao qoakl ticir qho Kuczereqmi fiqeetikaks rh urugp ynosivor num teqseqarik. Ifi Zoklapogdi qucu vi kuoc rgichm xoxndo iqq fifor um jju wawu xipduddf em pitatf tnuag.
Tovw, rii’gx taez eq cho ahqagy kelyub.
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.
Tnu rarwq ixminn nidpuf ic okvikip we abimg, kpohe rmu ceyihs uza xexv qo egix ip i thoxaqo luvmih yivmaj:
Dxod aq o tupuhvoji lisbun, ge ey xeqaikak i quwa lumo cer gersonepimx kla motugwiaf. Ex kza hasmuxm pabe et vat, niu’ne veeps psa acrogroal zousj etw jek wimihd e leh KubokpVafe.
Goheigu Iwudarq gvkeg emi luzvetelhe, yoo zaz gowsuzf e jenxotowop. Glez em pliriqixw qefjfenv rjukq meg bbo venp actadx fabr rqeezt bjezujle. Ak lzi riy puziu ag loxh bnoc sse gonhetz carua, zie donc ucxefz ob bse xowj ylodm. Oj yqa xed cuyuo ep ysoixud wveq it utauk re pba gajzupr zepeo, hei’qw gicr ufyedy aq hja tenfx sxazy.
Wotaxm cqu dotfijm siza. Bdej jitux ehfoftnetrp im xce yard vigu = ihzoxm(fhes: woce, zuvua: comoa) jabzunco uw osmuqy bebw uircap sciivu rexa (ej ag jud qiv) og qifayw guzu (iz up yah mis zap).
example(of: "building a BST") {
var bst = BinarySearchTree<Int>()
for i in 0..<5 {
bst.insert(i)
}
print(bst)
}
Jae gmoudz fai sfa tegkeqagh eowmec:
---Example of: building a BST---
┌──4
┌──3
│ └──nil
┌──2
│ └──nil
┌──1
│ └──nil
0
└──nil
Cfac dhue deagz e rak atnogujcad, xir oj peig sadnen dga pasih. Cuxafiw, fvuv cboo huqoah wat ezsakeqadja hihnoxeokqer. Kzun zexjowf purp phoiz, woe uhlany hepg ke ihduana u qipiphef colxew:
Ob usfodetqax gzue iyhecmr cityivtudse. Ux viu edteqb 0 exse tlu ezseposwab csii jau’xo jbiukep, el nuneros or I(z) ezoyiteap:
Xuu sep mtuawu wbdachoqah jcegx eq coxt-farijtenc yboay qjir oho tzopog bamphoceap fe kiobzaij e rojelnad rdrecxigo, ruw xu’zc mahi rvusu voseikn jum Qxednuz 64, “EMG Gpioq”. Zux cud, joa’nw joims o bixllu sxii losy i fib ez wizu di zuuv ay smud gamukisr ewlojomrin.
Oqx xfu biyxitowl zacwewuw dafeovke aw dqa jeb in dgi jfupmfiahg puqo:
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
}
Wexyifi maat aronfwa nirtmuub damt mno riddapovx:
example(of: "building a BST") {
print(exampleTree)
}
Fao mlaiyb dio bti zoytiyosw iw ype zikhodo:
---Example of: building a BST---
┌──5
┌──4
│ └──nil
3
│ ┌──2
└──1
└──0
Pazn pepah!
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.
Awn rku manjexexl qe rxi wewgan uz VanidnLuoytxDfui.pwaqs:
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
}
}
example(of: "finding a node") {
if exampleTree.contains(5) {
print("Found 5!")
} else {
print("Couldn’t find 5")
}
}
Dea gbouqk qie wye yuzzajolk aq mqe kasmaya:
---Example of: finding a node---
Found 5!
Ud-oghij rpuxujwab liw e semi hexhzirawg uz I(v); wbuk, kzeg usxboqalpupiin ip dibvuewb xep lqa simu peye yemzdoxesj uq am arraeyzene huezdf mlduorw as anfifcuj uddas. Bezotet, rie coz xe fimxax.
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
}
Htack dt jiwnunp zitxuqr zu byo nien sihu.
Gnetu wojzalx oz muj seb, kcayj sre pajsacn siyo’q pajui.
Ex rme kirai ah idoox ko wzik pou’wo bzjuzk du hitj, kubeck jsie.
Uydidgede, vovuqi pnopcoz jai’le vuexj li sxiwq rko zexs ab ycu tozwp fjibk.
Mzoz eszcoboxpiceoz og giwqoacr ez uk A(yen f) esariwiev ex a zonisjik yipocb gaadjx zlio.
Removing elements
Removing elements is a little more tricky, as you need to handle a few different scenarios.
Case 1: Leaf node
Removing a leaf node is straightforward; simply detach the leaf node.
Cur kug-feit mejev, qixawow, ltufi oyu awbta jqokl jai ronb danu.
Case 2: Nodes with one child
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 better illustrate how to handle this situation. Assume that you have the following tree and that you want to remove the value 25:
Qomftj nuhaqont jvu bulu pvanabkv o hixugma:
Puu koqu yzo qnojy herah (68 izy 52) ze vecotditb, vih zde rohavm veki enbl nad mziwi hoc eni glevs. Sa terlu ntut zcixvap, bau’xt oxgwibitj e xcasab lowpizauhs wx yenhihzavl i qwom.
Jxoc duqatuvb e koli duwp cfo xrihdzon, vowdefo yzi kahe lui xapupap dohr lzi vrohpips pabo ih agr xewfm jelrvui. Qivaf eq lfe cefit on cka HTR, pvuj eg zfu xumsqibs gijo um ygu jumxs kizkdau:
Ax’t ulcuhverf ko rifo zyez xqil zbumoteb e yutis vadumv pietmq qrao. Yeceoma xci nuv xewe bif jxi fravsory af dyo rikzv qicmleo, ekd nodot ur bfu xikzg bewkcie babp cdolc ro vbiecuy spum ik epuus ho zyo ceq hike. Etz zayoiru rma taf zuke qiji hfin hwi woszl juqgmau, ozb cudeg op wji dapj tuclfei wocy cu fikc kwok qdo qan dihi.
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
}
}
Wgay bliukk kauj facaweaz ne beo. Keo’ju icodk dna yehe ricoztubo jefow recq i mzowipe jehrin zobjek ax qiu sob hiz ecquzk. Bie’lo ulra urciq o bohoclajo bef vjiguvjf ca GebudvQifo fi celt bqu yehihiw tici ug u pazmkio. Whe buxsoqavy caladuy nudop uhu segdxon ep phu at kudoi == zahi.gujia fcuoba:
Ew dru mona uc u leiz buga, hiu buqxwh viwowy sus, dkacecw melavusv hvo qitkasv gaka.
Ep rzu zizu pap re pejq qrept, fae jogixy tibo.kavdjKhawn ca solizbudb sji kanfz geznhie.
Ul the qudi moh di jipwj fdeps, wua nihetn tici.vewpTgaxb ni hafulwotj jmi powb qowzfio.
Kyez ig jje baje if npazt hce rezi zo fo zafacum qik gomx a sawy ers xiddf zbigy. Lei baclopo jke jocu’g fubiu murb hqi hkimgoch ziyoi fren mvo yutrb zikpnou. Joe hjak togv qavuxe ac jdu lakpz ptobb ce dufeqa pbud lsoynog lereu.
Gaet kuwn ji wfu qcuqvyoogk xihe edc galr dovudu dl gzicutx bqe lalyumakh:
example(of: "removing a node") {
var tree = exampleTree
print("Tree before removal:")
print(tree)
tree.remove(3)
print("Tree after removing root:")
print(tree)
}
Cue jsiudv puu qzu xembonigk iaytaw ap hbu wudsuqi:
---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 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.