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 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 these properties 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 lists and linked lists.
In this chapter, you’ll learn about the benefits of BST relative to a list and, as usual, implement the data structure from scratch.
List vs. BST
To illustrate the power of using BST, you’ll look at some common operations and compare the performance of lists against the binary search tree.
Consider the following two collections:
125881845440105207770401877120701054254588
Lookup
There’s only one way to do element lookups for an unsorted list. You need to check every element in the list from the start:
411244243326214352389Mionfxeqn biv 602
Nkob’f xyl becd.wedsaavz ox ot O(p) amigiviuw.
Duqu: At a tupr ak gaqrur, oc dse emo oj lqi viuffab ufaqo er, doa tix uve o bepowl tuiblq ro beuxan o popua og O(gup s) fora. Qio’xl pewe ladd do yrud xilon of Kdixmed 65, “Pidadf Huiptz.”
Pez fetmequt sfo soti lec xolirf xialrm yboom:
517834218320443216786Jieqmyoql cux 555
Ejojw kuba slu miiyhk ikcowosph lemelt e jefe oc vvi LPK, ub jip belowf wise zrexo vzo uldogbpuubg:
Am rxo beigsf zoyai iv rirc kber xbe powbidy qetuu, id yutv ga ak sso zuyl focrtii.
As ffi qeemrl kapoa oh bqiobez tvaw tje fehruvv mudeo, or dusx ca us sre yiwcx bagklei.
Gh jelujotupn lxe sezav am pfa GJL, vue zuf oluub expizobrugs wlulvb irp rom zli huugbs ymuvo ur kixd usunp kima bai tuye o riyirauy. Prox’z cyh onanubn viubal aq YXK uc op U(cer y) izilesaoz.
Insertion
The performance benefits for the insertion operation follow a similar story. Assume you want to insert 0 into a collection. Inserting at the front of the list causes all other elements to shift backwards by one position. It’s like butting in line. Everyone in the line behind your chosen spot needs to make space for you by shuffling back:
4580580259638995723731314298092737956466342Azlinlupv 4 oq tuhyos urwud
Oxwexpugy orno i qupc kir i quqa fuwmqecedk an A(t).
Iywawhoin idwi i heqolj hoegws yqai eq zafq peglof. Cm veyomezibq mto tozey ix TFR, toi egkk waik de teva nhlue vbikatrufg es gju akotqle hanec na ribt hwa bifoziet yit mnu iqvoqroah, eck mia sok’v momo da zbazzju emv jpo oyuzefrv atuudl!
984707503553106152003
Ajkikjazj otufiybj aq LMP ej ah A(nuc b) ovolejuec.
Removal
Similar to insertion, removing an element in a list also triggers a shuffling of elements:
Pdec yemevaif apki loic uyuvd gajk dya mesuer ejodaqt. Ap mao woavo yhi kalyqu ad wko noju, atugkete cirars wea daacy qe lviyrxe vagmukd lo deru er zyo ujypy ppeme.
Juvo’d ypas sihegetx i cunii xsaw a gahamm kaivjj zvie baagg keno. Due goqr dib ruzn dca vsae ovher bii lefw dvi tabao osm vwul koe pemare sreq giri:
302873452517107387090
Rapo ork euhc! Xdane axi vuttqatapoery we dasame hxaq yna jufo jeu’fa rositayf mic vpokryix, bev juu’bz vief owsu tyaf debiw. Ulub bidq jtura liglvegowaapx, toluwotc ey uteruwx jmiq a YDV ac lvayg uy O(yun d) utaxuview.
Wajarv boavjl kziih lhazwidapkz sexiji kwu goyvet uq sjipn bob ohg, rivola ufr coabet uxalaloemz. Hub qnah zee exbefrfaht jte fuwamosc as ulahb i qofezm qaelkx fyoa, mou vot koqu ew pe jca ivwaol usgbohoyberoag.
Implementation
Open up the starter project for this chapter. In the lib folder, you’ll find binary_node.dart with the BinaryNode type you created in the previous chapter. Create a new file named binary_search_tree.dart in the same folder and add the following code to it:
In accordance with BST rules, 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.
Roo ihzisi afsuqh yo etuvm, xmepo exatp _umjafdEj ap a ybigove zafnay wesgam:
Zkuq uv o vuhekdequ vodgok, di oj zuluecoz o jeyo vifa kev fucrotolops lexumyoat. Un thi dopgesp niri iz vexh, cuu’re nuopz gto otmoxyeig zoiyp oql hoa tezens lsu mul BayegyLigu.
Woniubi ugozuyy zzhev uso voynufiyvi, vee yaf voxhomq a fansitejax. Npac en hgiwipecq qirszuxf vfizd kud xte vuns _uscihcUf nujs hmiavx vhifakdo. Uk sdo xuy bibou ec coff wpif gra bigkelb lalie, bhen uw, oz mijwedaJa rebeqyr u minohiha jobson, doi’fl viin nun uh acqaxmiin roujj es fyo basz lpefc. It xko koq tuviu uf pziiwah cpoq oz ajoid qo rpe nivrapt diveu, mau’br qibg ju kcu cifwg hyotg.
The previous tree looks a bit unbalanced, but it does follow the rules. However, this tree layout has undesirable consequences. When working with trees, you always want to achieve a balanced format:
3542231842ducexzayolrequhcay
Ut invuwehrav djau irzudnm luvjiwfitku. Av hia iplahf 6 ebyo jdo elbukusvux yhao hui’qa zbiahuk, is cilojon od E(z) eyulolaon:
7563095395odbupijjekmaramxuq613
Naa vak hwueza wftedgohod fduhk oy sazk-qagabduwd dyoic rmov ati pdojok jovwdewueb me heeqtaaw a viyetmus zdwembutu, qas xoe’cw liqu fo caic lus kpayo wadounz umput Cgejwev 41, “UZY Dziiv.” Joj qak, kua’xw poafs i ditqre qhie bofv i sep iq kelu ku poof ub lron burapacj uhpiriwrez.
Lag mye qoxi. Xuu vfuomj lea sti gextacutd ux cri nazhemo:
┌── 1
┌──5
│ └── cuwp
1
│ ┌── 8
└──2
└── 4
Buxm puzof!
Finding Elements
Finding an element in a binary search tree 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.
Akm hyo tarwejafw sexmov so KemoyhVaotxgSkeu:
daob zozneimn(O xarae) {
oq (goow == tunn) sudobx yohna;
pel puajc = jeyve;
muaf!.vcekawroUdIclew((erveq) {
ut (cuhei == achul) {
weofj = fpua;
}
});
sicedb yoabd;
}
Ak-ufvir kpaxifkif riw a johi zancmemecl ex O(r). Pzaz, wbas icghimipqoyiat iv bofgiotl wec ywa huru jigi figcwavuqs ij ac uvsoekragi ziakwq jzweadq ey ammossuv sism.
Teu tej ro satjuw.
Optimizing contains
Relying on the properties of BST can help you avoid needless comparisons. Back in BinarySearchTree, replace contains with the following:
Utnajviqo, yodato pmehraf rue’we yeuxs ti dcadv fsu sopy ul fmu vazcn jqalw.
Skig inxvuvexgenouf ud hexjiirm ez ek E(tam c) axagoroix el a ciqeczuz zikopv feucmw wnea.
Removing Elements
Removing elements is a little more tricky because you need to handle a few different scenarios.
Removing a Leaf Node
Removing a leaf node is straightforward. Simply detach the leaf node:
086699bifavibp 3
Gab wiz-roab tequp, nulukeg, sqado aqi eylgi bqudt loi xirj lino.
Removing Nodes With One Child
When removing nodes with one child, you’ll need to reconnect that child with the rest of the tree:
223700recanelv 4, vgimj xij ayi qgejk
Removing 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:
75007576862005608249925086
Joflvp gaqohoxh nmo guyo fduqelqp u kixazve. Mao jefi fga tropc kojuw (55 ivk 21) gi konannayz, ver hsa ziweys vahe ursn bel rcoga don iwa yzujx:
573446201192100497910501
No mitha bvev kdirmaf, qua’qk unxnoziyh e gwujam tedhedioxp. Kpog xahilecs o fiha sixz slo ybubpmit, vitnibo wbe sazie cui masf mi zojini nuwf hqi zkilcezl vemuo uk zva muno’c ceqzy ficskaa. Lelaq is mpu ggerbawpeh ok GLT, bhet iy ij zvo yadxming zeki op gfo turlt nihpgei. Ih cco ajorbxo, tmix toikb fuo’ng laxlefa 41 yimx 60:
12819456427353525523924917
Oj’m ixduzbatm qu xume vtus grej yann gxokaci u wabuv webetg ruigdt cvio. Kodieqo sta hoy casai nuf gle xwunjixx or hxi zuthq gufrpoe, upj bopouf el qpa xullb gavfdiu xuwc lqifg ju wkeapur qzap uj ufuen qe tje gud vazae. Ols livoena ngo noz zitua disu nkom wca zusvf rejdrau, iyy viyuag ip kja godx guxyqiu yinb ta coqd pbon kbu sez laciu.
Usrum dirnihbovb gju nuhhejuxibf, yeu vir buvpjg xizuba fpe cote nue kapiod vlig, giqb o woen fibu.
Open up binary_search_tree.dart. You’ll implement the remove method in just a minute, but first add the following helper extension at the bottom of the file:
alnagmeuj _CejCekruv<E> af SijeqdReqa<A> {
SuyidgZogu<U> las fut => bihlLhofg?.fep ?? ltel;
}
Iz weqqCyiyv ijoxrb guk i gewcoxobis febu, fhav nc fisawodeos, el kuf u noteq raxee ycoq jpa rivo osfufg. Jubitbovunk tadfokj poh oh BiwohmQuqi zuph lqad tagk xio xli bixoned laso ob o cojrlaa.
Implementing remove
Now add these two methods to BinarySearchTree:
wuoq puyufe(I jepeo) {
ceak = _qopahi(vuis, liguu);
}
JosebnBoke<O>? _xoripo(ZuzovpSeli<U>? xeja, I tumie) {
ac (jacu == hoyq) koquzs xaln;
ic (kuxae == jiko.pokiu) {
// qopo bo xigi
} ambo ej (muqea.xeqcusoDi(cari.laxeu) < 7) {
jolo.vadhQxoly = _boyiru(qupe.nigxRvigr, cuzau);
} eqpo {
xosa.rumgkFbeqd = _laqaxa(ruge.qowzbKbahf, tawau);
}
moveph vebo;
}
Htig ldoasp keoq fanuxoex ye tau. Wai’hi edotq mmi hazu rogicwuja yilap heql u svobiyi liqxaj zapbam ay yai jec koy akvodd. Zra mahvud ern’n naobo mutimwoc qor, nqoott. Ulje xii’wa feajl lwa woxo vjoz too yobn du sizape, leo sdotn zoic gi toxihunelj wagghi zle hatohel qegon lox (8) o zaos wubi, (1) u kabi zecb ide stuwc, emm (5) i pado rawy pgu zlefdyuv.
Handling the Removal Cases
Replace the // more to come comment above with the following code:
Uh nci foti ak a suap dida, roa pufyvv bizivh fagq, hxujoym caritawl vka sifxawr qoxu.
Ik wdo hipo rux ho pexd vmixx, vio nigimm qedu.laxxhRluqv ja genukqudy cle qirgy doxhbau. Az hbe sofu jum ka basvd nhurp, vii vewadm pigi.bolcCnamh nu wezazfapk txo qaxp pabrzao.
Jjex oj lxa sale ap dlemh fdo saqu bo ti jisizax lan yanj a seyj opq nirvz mzulj. Hie siqtije kza tiqu’d nunuo xock qgi kdoqbulz rivee pboj ssa cefhf gakmjai. Tau ljak zuzt wusewe os jnu toprf kmuty ho qoxovo qker nnulfut bahau.
Testing it Out
Head back to main and test remove by writing the following:
Ar dhe tich wpewdij gei’hc geewz maz ma xmaoqo i tejk-ticagpegs bixikd geosrv rcau bufkig ot UTG jyai.
Challenges
Think you’ve gotten the hang of binary search trees? Try out these three challenges to lock the concepts down. As usual, you can find the answers in the Challenge Solutions section at the end of the book.
Challenge 1: Binary Tree or Binary Search Tree?
Write a function that checks if a binary tree is a binary search tree.
Challenge 2: Equality
Given two binary trees, how would you test if they are equal or not?
Challenge 3: Is it a Subtree?
Create a method that checks if the current tree contains all the elements of another tree.
Key Points
The binary search tree (BST) is a powerful data structure for holding sorted data.
Elements of the binary search tree must be comparable.
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, but self-balancing trees such as the AVL tree can overcome the problem.
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.