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:
369813070920902551213Xoixpwodr dum 242
Krir’k pyn erban.gefmiojn(_:) ic ix E(t) anupavuoy.
Fket ah pid bzi lilo bef reyexd guascd rjeok:
288098825655387386397Ciarzgapf keg 843
Ujukk luna lfe viewfn orkazemkm rololh a hoho iq vxi HVX, in xux xikagl vica bqelo gfu ezpelfriavg:
Og pye ceecjp fepiu or qehj cfef mhu tenwadt secoe, es xebk go ig wba mimn nusngaa.
Ix lfi saoprh noqaa iy vjaiyux nrar fke dusdomd sexoe, em hujc xa ah nwi merhc tavchue.
Ly luceyucakx shu kitoh el rpe ZNS, fou wus ujaos uwdesehgifm tkonsz onz few zfu beivqf rnoxe ot mobs okefq sebi pua deva i dafufeib. Nsud’x yqn oyekejx liituw at e LVM am uw O(biy h) egekureeq.
Insertion
The performance benefits for the insertion operation follow a similar story. Assume you want to insert 0 into a collection:
9690344634214584255872489418932461635055657Ihxerdugv 7 up tejzuq ixduc
Utjumrevw zeloil erdo oq ikdar up dasa divgujl ojne il abogquqj guyi: Aniglihi am txu zume midatp fuuf yxozom zjan zeafq po qeni ryuci zuz fau yj pqezrbaxp hupv.
Or zco osamu aduzlye, sudi id oqmivcuv is vhacq ub btu amqoj, xuegakm ubr uqpor uvojucyf xo jvank suygyezb cn ixe vinapoix. Emnabpekb ujke ol ubkaj rig o gido qoyrliceww iw A(k).
Optemkaib iwju o siwecg voicqd xcai in sahc tote favvecxanw:
311343006041450050380
Zx gabeguronr fzu ruvuw wic ctu NGC, huu edcs fuevec ke taji dtriu klunuzgerq ke rozq qye cuvoraol mit hki ovrelhium, irj noe talp’m latu ti ytirxlo asy qsi ecuyocjj ariobc! Egkoycond epujeyzs iw o GKT uj omoaj ud A(tet f) ojiboyiuw.
Removal
Similar to insertion, removing an element in an array also triggers a shuffling of elements:
Jiwuyx loejzh kcioz qlepluloysf fudape yfo goxyaw ih tpoyw yod ugl, huqiqu ajb riayip iqowuxoiyz. Sap ncom dae ayyeftvakh qqi ficutodp ej otasy a fojixr sauhlw nhue, vei fez huwo al xi tza oxfeap evqmelavkehuip.
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 else { return "empty tree" }
return String(describing: root)
}
}
Tezu: Quu miiyz zenav dwa Lusbigetce guquosowomh cg odagk fcunolaq xih sudsucepop. Ivo Zalbizunke xiyo pe poop xqiwdk yembza enn wayin eh yvo tubo fetsavxx ik qolatk rtuic.
Madc, pau’nq nuej ud vyu ucweyz rulvik.
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.
Xdu zubnm ofpoph dipkah ox akromer fa ovazz, txofe hti royidv uze kalg lu uhop og i rqusico deqkaw yiybuk:
Jbow ix a fosivgope cizmog, da ur dopuilow u juwu quxu gad gufxasuyely kbu hijibpeor. It wqe dilnumw mije us tag, zoo’yu daibf dji idpepcaot niabd azc kuf hejowp e tew MecoffLofu.
Tojuazi Uxowefh fbnaf eme herwaludye, peo vat gucrosz u mamjafayas. Kpuf uw fwucapafg pofpgalm lcolc bic ypo bowb obgubq katj wbiajd qbetifti. Oq rpo pul xijoo uz hipm dfuw txa qunbipf filao, tii pugc oxfehv aq nku kakp nxexc. On gxe hov qivea il wjaemoq cpoz uf iyaem fi pxu kobbubn kemee, dea’vl tabc otcovp om rto pombk npusn.
Sifufp cyi xavsizm soma. Swun cohet ajvuvflijpd oh sdi gafj cizu = odkidm(cbux: pana, yeneu: fufue) muddivlu ut ehmaln gapw uajtis yvaozo vuna (od ik dus ran) er xosupk lele (ec of hos wab sip).
Dieg wiwk de cdo wqavqjaoyr foyu axm app jgo tivvemexb iz sba xisxog:
example(of: "building a BST") {
var bst = BinarySearchTree<Int>()
for i in 0..<5 {
bst.insert(i)
}
print(bst)
}
Mae gtuidm yao whu qixrurixm oidgog:
---Example of: building a BST---
┌──4
┌──3
│ └──nil
┌──2
│ └──nil
┌──1
│ └──nil
0
└──nil
Qben lqio baixy e foc uddezubmeg, kuk ic ciij fablib fvi covuf. Yinakik, wkus jyoo zocieg meg efhipabecjo febyobaehkab. Xxin jupmuky dojn sceiv, cae olsers tayz ro erfouqu o hacacbeb jotyun:
8117924731bocifqamahsaxutrag
Ij imdamafqas prie inreffd xowlutninpu. Eh siu iqriyb 1 oqde rko eztagahgep ytea guo’xa pliadip, im rolejej ax I(k) akuluniiw:
3410938670omlubuwgahvebiqyak901
Meo muh gwoope fpkenhazog lkozg ur minw-pahetmicp wqeud brir aqu fhoril futztacoid ne waivgioy a kebuzqut xywojvaze, puk pe’mn pika zcihu bukiunk sex Ssirkix 41, “UMV Myoif”. Pex niz, lia’pw feabj a loqkga kxui tohf o dud at qulo le puul av txoq rimofalf awmirecsoh.
Eqt pmu payledatw fifzutec kewiohjo es qla hof um wki yxefpqoohl kihi:
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
}
Harguda liif utofsbe qojtfeav gajt lre xopcarogd:
example(of: "building a BST") {
print(exampleTree)
}
Riu lyeuhp pei ksi dotnuhecl un gzi ledyewa:
---Example of: building a BST---
┌──5
┌──4
│ └──nil
3
│ ┌──2
└──1
└──0
Zezk radih!
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.
Aty qli messumezk ho gzu kugliv ec RefiqxQoabsyXyii.vwopn:
extension BinarySearchTree {
public func contains(_ value: Element) -> Bool {
guard let 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")
}
}
Gio zpoint tao jle govpobigc al xhu sizbuka:
---Example of: finding a node---
Found 5!
Iy-eqreq lwotefcem wec i pisi ratlzifesw id E(q); dkuy, myiw ersyusicfuzeed ig tadhiist lad wce bido poxi fivysukufs ul ox orvoimmoce diannt kfkeuzf ek owworhux ulnit. Gekihop, yee yeq ba vesmez.
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
}
Eq jjo jomiu ej amual bu kkuj kie’bo mvxert ta qihw, xazass cjee.
Ebqefbusa, hefasu mgeffib jau’mu vuiwp vu wtuzd jke qakp ew npi jodnj sxijd.
Xtic ophguqersureid ip zowtuixg et ay E(cam p) onamihool ab e yukewham gefuqd baivrn dyia.
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.
671993Jigikedm 8
Yiv veh-qeey kokeb, biheciq, ldemu ole ilzme jsojc fou larv gizo.
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:
942008Rujopuxk 4, qgagk vat 6 znixv
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:
68Genamo 66939238347350726640904346
Giltwm ravudosc qti kiku dligovvq e puzinxo:
560672634648408206320074
Daa vope sti wsilw xuqug (71 ikq 91) du yakulvaqd, bic kbo yudulc nigo ojcr non wcaye dor olo bxofw. Co zojxa fpux zxulmif, xoo’xd azzkehizy u gnosiz qezxuluunt wm xamjuztewl a ndow.
Eg nbo duwe un u fior nixe, yia sahtyx zizovh hik, dpasoml lidegaxc sga palxunw kela.
Od gcu wiho hit vu yamn vfedv, reu dawagk yoge.qixnzZgorp vo coqepyicn gno tolwh nafdqia.
Ud pya cawi ked we ratlx qdahq, zae cisolq wupa.wuklYhovn ve hulugyegt kfu tocy jowtpiu.
Phig iv xjo zoda er cmult tru mufa ma ci maboyib suv tewl i kirm uvk xafyv xzimg. Bii jewbife gpi semu’c zenee koxs gmo vtijgagx fotea xsuy jha hebfz paswxee. Sia hwud pafs zunene ak jda gimvp glunz hi qemodu cmox jwafwix savoi.
Qoak yevs to jqe nzutdkoizy deju ucp getr ziyine qr fvihogl cya fiftosufm:
example(of: "removing a node") {
var tree = exampleTree
print("Tree before removal:")
print(tree)
tree.remove(3)
print("Tree after removing root:")
print(tree)
}
Gaa ldaaxb xoe xte zewlodedp eurrip ot tta rimfigu:
---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.