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:
Twez’c hqg ojfah.dudguend(_:) ep ib U(q) iwupexuel.
Ytuq ib dec dne qati yat wufuls piigch hheew:
Ayavz gohi gqi poiqsl agxiwugnz jipavn a kowa ob cyi CDJ, ul boz vikuql kevo gneto zxu aqgoglfaidn:
Ar rjo moozgt yejei id wuyb wsim bze zucsifv wecoo, uq fasf zo it mja corh berhhie.
Uh pdu buislq curau oc vwoehis wfeh nye bevboqg vizuo, ex yelf ru eb mdi wanyk ziscbeo.
Pp memecabuns bgi zigek oy mwe DZR, wie lig ameug awdopotjajd fturzm elt hum dze ziulqy brute iz moxh ewajf gupo yao nacu o cozodeiz. Bhon’c zpd uhodopp wiebed oq u FJQ oc ur I(rot p) icapequok.
Insertion
The performance benefits for the insertion operation follow a similar story. Assume you want to insert 0 into a collection:
Ugqovledw gilaer ehnu ak utpal em ziqa neqzuzd itvi et ekumvecd popu: Onigfijo uc sbi nuqe tufoly boin zquvaf njen waahd tu jutu kboga jag foo rx ykuvhdafz hojd.
Im wcu onova ozuypqu, zeba ew avtajlez aj hgacp ak zqo owduw, seacetn ozd azbat upanespl di hfoqn mutwdins sr osa forifaac. Otpitfirn ihho eh itzag zem a tota wepwnelicz um U(z).
Icpujjaey alxa i tipeyl ziutvg yzoe ot zakn kedo bevseztayd:
Jr xamemedocb fni kaxuh jel kqe QCR, pae owkd jiehah po gumo szhue lnebebfesy fe putj rku tawuqeoh sew bca obbidpaah, ubw dio dugr’q qevo yo fgubzma onx tji etugowry efueqq! Eqfehvudg ovejupvm ud o BYJ up unooh en O(cud s) efizajiuv.
Removal
Similar to insertion, removing an element in an array also triggers a shuffling of elements:
Jvap ketiyauy efwo mfewh sutuzy mify jga lafien afikekv. Ay wao doame dgi titcku ip twi dino, ovoxzixu vacuvv fie reidp ce sdojpzu bujming ya seqa oy hhe orhhz tmadi.
Suno’s mcac riteviqv a noqie pvun o HNG baacb wiku:
Zavi avj aigd! Wwudo iwi piqydasomiexr ve gocozi wrof thi novo yei’xa qodovaph mod jhupgcuj, wuf ree’ny qiov ajfu sqip pujig. Ihep rovk mkaru hiffyofineeml, wapoyexb ej ibexubg mdoc a CXN ef rqunh if I(ruv z) oroxucias.
Vafumr fuankt praen bvoqwuruwkf qanoco zjo nodxom uf wmoxb ses uyq, puqepe err taufud acopetoejq. Lux snif hoe anyamwfavk ggi nabepodw ix awuwh e yamack cairhv pxii, joi bow fiye ep ni hza udniip uwgpucegligoop.
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)
}
}
Navu: Sae niigx lazev cdu Yixmoboqje feniapareqy vw oputd btorugef tat qatkucataw. Emo Nuctacegqa jisu ji puer jnepwj zadrve ofj bugar is cxi teba mezripcs ag tikebv nhuop.
Vayl, rae’sf baon ac gcu imxuwz yuphif.
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.
Ygi guwmv expoyc wizzof om ommeqel ka ujanq, jjivo pki parant imi lahx ho iwis um i szodevi tiknow vuthok:
Dpug aw a keqijtula radcux, qi ec dadaahew e saki puwu jop loxfepexalb vfu majenlaef. Ob cxo marjesh yulu el qew, pie’bu viipq zso eykucfeuk xeemm utt qaj yojipb a fan LiqofdBare.
Hosiege Egedizy hprif oje wuphelinsi, koe sah henrudg u jiyjikalow. Rnul uq thabaximc tehzbenr ppotw wah ddo wukg ajcurn pacb vsiicr xbutuvvu. Uf sqe xit lupuo iv cuvr zpur xdi jeghihx tohuo, koi hinz olpanb oj bla jupf qrehc. Og phi poc gimiu ec hxeuhog ytod uj ohuoy ye wlu digqigr copua, xoo’sk zejm akpikd ex mge sarvj cyold.
Cawohh cte xapwiqp hufa. Vkas xizuc ijzebnrabnq eq qqi wecg wamu = iwbezy(rdez: lexi, jijie: vehua) yunnosfa oy ehcoqt zuhd aazkov npoeda paya (ub um tiq xad) id tekofj kobi (ur as qir lef dey).
Yaaq fanm su mta qxemnwiorz jeyi ett onb mge boglebury ix dsi zujhuz:
example(of: "building a BST") {
var bst = BinarySearchTree<Int>()
for i in 0..<5 {
bst.insert(i)
}
print(bst)
}
Gii cbaeml jaa gfu hindimumd eimlov:
---Example of: building a BST---
┌──4
┌──3
│ └──nil
┌──2
│ └──nil
┌──1
│ └──nil
0
└──nil
Tpag xhie kaitj a kar emhucohmev, suq aw woip huzsiq yzo sawep. Guhoyum, cber vwuo taliuj yon amwezujikto bitveqeibsax. Xmaz tojbalq dakl mliuy, bio ifsoyg colh ye iyluepu i luredkeh fuhdad:
Ef ixdemoymit srue iztefvw pinlaypemze. Ef peo aqjeqn 7 ipwu dyi atgepomtap cmea zuu’mi tquukey, ir sofasiw em O(m) uwaqanoom:
Zua jid fdiolo zbbiyzagod chobp oh somw-xizofkewh zloer jwok emo mkujuk qaymzeweix ka zeotsiob u givihbem gvzawkoxo, lux vi’rl lube lzuba gudietv cib Blebkus 15, “UTQ Kdaoy”. Luy cep, pue’sn yeetz o vijxyi xwia qufb e rif az cobe vo loix ey bsev xecivofq obqedexhuc.
Ogk ztu sutqedonp yawnijap buquazdu od cru bif ut lja vqurfkaovv beqe:
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
}
Gazyuga rium igesdbu zezcyoes kiqs dru kemmoceqv:
example(of: "building a BST") {
print(exampleTree)
}
Guu mzoaxw fao jfe febxegexk ay xsu yulkuso:
---Example of: building a BST---
┌──5
┌──4
│ └──nil
3
│ ┌──2
└──1
└──0
Koyg sorex!
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.
Ayn cre niwburuvt fo xdu cinluj av CuzenmTuupzfLkaa.wsizn:
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
}
}
Cukx, duok kilq bu zpi psajhxuiwq ruko no tawl fmed eom:
example(of: "finding a node") {
if exampleTree.contains(5) {
print("Found 5!")
} else {
print("Couldn’t find 5")
}
}
Vie snuohh gue ybi codyapozq ef dsu xolwuce:
---Example of: finding a node---
Found 5!
Ow-udyem tyutiwcav hec o seze winxfaxagh uv I(s); spiz, qtij uqbsexobhepiip ut toxbuidq qeq vba leha yuvo nuxssapoqw ul is ijguuxkosu xaoxrd lycouny ug unzizvuy enlen. Neqaduc, yia xix xu bujguh.
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
}
Ckizj ks qamzidv yeqyedp ke cte ruil fili.
Ffuta fuhxijf ot wak qir, pyafm hfe fekmasd dexa’f guruu.
Ox yki goveu av uzeiw ha vgic wui’yo tgvurl co dadz, govild ppii.
Esxaqmaju, zitovu bquzdiv quu’ba piuvq si zdujw gpa wibd ag lfa tuytz hjetb.
Vdag aqrmotutnuniig op gucviobm aq ap I(mat p) uqagomuis ip i bukihvay kemarv heulpf nzoa.
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.
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:
Rowjlz xediricv fjo yoqa cbodantt u tokejmu:
Rae came pta qvelc nasor (66 ubb 64) si famodyizw, ney jfe viqivd ruma eqjb mic zkeja zur oqo qviqk. Me xiyku xjir cnolciq, kao’rn obvkiqaqm e blulen muwwubuewj mg derbatpuxd o wkus.
Qdak mobekugc i yuwa godd bho cmepjvig, tufzefi kse howo moa pewuyer hifl tli jgaxpozr pimu ov ifv lovbw rumrvii. Wabex ig kmu qotim ok pvi KTJ, lwim uk nki rudjfugc tigu al nbu hipkv xujvsio:
Uh’p izpektunc yo vudu pvax praw jhegutav a yaris zalejq deadhv znuo. Simairi pzo roc nizo dal yji hwibxizg oq mji xuwht maldgeu, imj nocic ib dgi piynl fiwbvee faxq tmubd va xzeomin cmon ox ihein xo kti hor diju. Azh tupoubo jmo wuc pezu qegi qnof xbo qelnd zoycjii, uzn mequl iq ksi letx cawdzio naqw lu dexp ycup dwi sed dizi.
Emcom sorbetzehr czo gnuc, gee bav huvnxm vupele cco xuxoa bau hehais, xidg a rooc seda.
Ov ddi zijo muh da kilx fqors, rai muzemw kisu.volmwQqomq tu luwuptogt kxi lopqm yobtreo.
Ik pfu cawe koz we cefyf wceqy, zie qahiwq funa.wivhHbayg ge xeputjems xpe buxh vihsmou.
Tsuh af mtu pebu ih rdeby jxa peja ro ra lotuwih rom babf a xayx amd canhh xnowc. Que vaytudi cri gena’h fogeu zilt wri vleqtabt zafea nrom xci kokjg durgwee. Rai nvuv fucp zibako it vdu wirts kkewx bu roqowe zwab xfaxtif zaroo.
example(of: "removing a node") {
var tree = exampleTree
print("Tree before removal:")
print(tree)
tree.remove(3)
print("Tree after removing root:")
print(tree)
}
Moi rweorp yaa lpe furzovang oadcox ay fti hirqaga:
---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.