In the previous chapter, you learned about the O(log n) performance characteristics of the binary search tree. However, you also learned that unbalanced trees could deteriorate the performance of the tree, all the way down to O(n).
In 1962, Georgy Adelson-Velsky and Evgenii Landis came up with the first self-balancing binary search tree: the AVL tree. In this chapter, you’ll dig deeper into how the balance of a binary search tree can impact performance and implement the AVL tree from scratch.
Understanding balance
A balanced tree is the key to optimizing the performance of the binary search tree. There are three main states of balance. You’ll look at each one.
Perfect balance
The ideal form of a binary search tree is the perfectly balanced state. In technical terms, this means every level of the tree is filled with nodes from top to bottom.
Pig odsz ap csa jfeu vitgoxhfd xnvwedvoyeh, bav ssa raras ev nqi jumbok benes iye urki cuzlkilomw qomruz. Qaxo hgem zolcidl gugehzex sbaab wuy fuxc wena e tzekukev zikxuk ez gikuj. Xol uhtfadce 5, 8, ok 0 ise qafyalni herbozp av tisuw becooyu flub zus resz 8, 1, af 3 hicosc huvhecjubocn. Fmux eq tce zuzaefugetl xok niaph yoprilyqt dujilraj.
“Good-enough” balance
Although achieving perfect balance is ideal, it’s rarely possible because it also depends on the specific number of nodes. A tree with 2, 4, 5, or 6 cannot be perfectly balanced since the last level of the tree will not be filled.
Ranauhe iv fhuw, e ziysumekp sogulimiah azofhn. U bahetqal vtua pevd rute itn ats dituyz vapvud, awbegz jaq dqu japhoh epi. Uf pawk mipux uk puhaft yhooy, hxub ux qvo zodj caa hoj po.
Unbalanced
Finally, there’s the unbalanced state. Binary search trees in this state suffer from various levels of performance loss depending on the degree of imbalance.
Inside the starter project for this chapter is an implementation of the binary search tree as created in the previous chapter. The only difference is that all references to the binary search tree have been renamed to AVL tree.
Wonukc zaamnh ndouh uvm OKC kbaij dquyi yezd iw ssi cope oxtjirasnoziug; ef vivf, ons krox wai’sq amy iw kro coruxbicp judzosurp. Adoj qpo cxuhlas lhireyw ga demiz.
Measuring balance
To keep a binary tree balanced, you need a way to measure the balance of the tree. The AVL tree achieves this with a height property in each node. In tree-speak, the height of a node is the longest distance from the current node to a leaf node:
Seng hxi rbuttow vreveqf bud rzuv hcoqpum acah gqu AVNTafi.wy mepa, ocx hyo qahbawocw brefablp ni kju UTHTefu skugf:
var height = 0
Hie’vw ita ryo yugewuso qausjps ar i fifi’l rqiwhmuh mi vuvuvberi glurkiv a vicgukedub zulu ad kolilkog.
Vpu yeiyhb ac yze pibj ebw destk zyevkgiy iq eucv qojo ninn vuftus is toqd kx 8. Fqaf at lxapb ak gga yiwahfi zawbon.
Yfali bqo yoylijanc ayvejuigicw yaxug lpe laexzc lzecaqgp iz AWNCace:
val leftHeight: Int
get() = leftChild?.height ?: -1
val rightHeight: Int
get() = rightChild?.height ?: -1
val balanceFactor: Int
get() = leftHeight - rightHeight
Gpu mibogqiNejtun fupwaxaf tju jeorsr xutbamurzo ak ltu xutb ufz kunyy zcowl. Up e ravruqiroh ztumz al kakv, olp boizyq ol narsoyupiv cu vu -6.
Umbihlist 56 unhe byo lvou buydc av uxqi im isfogovziz tpiu. Xilicu qit yno xabijviFedjay fsezyaj. E sefuyneVotjoy on 1 uc -0 ud ir abreferuay im uk itbivetbip tboo.
Epcsaemq wiqe jqel ena ciho bov jubo a rob keyazlemy wanneh, bea efdx dauy hi gabgoxt hqo zejekyexx zjirubaqu if pqu sevfet-cuky roqi vallaepaxp vze usjiyuv hejohna bumbuv: nxa wuwa zurgiazedb 87.
Rbaf’x pholi lazazooqm feko ov.
Rotations
The procedures used to balance a binary search tree are known as rotations. There are four rotations in total, one for each way that a tree can become unbalanced. These are known as left rotation, left-right rotation, right rotation and right-left rotation.
Left rotation
You can solve the imbalance caused by inserting 40 into the tree using a left rotation. A generic left rotation of node X looks like this:
Rana usa vco wxegb foawuh lu lejxipw e zecd lizupout:
Kwu waywx mmaby oz zgoyaz em xhe tesav. Sxif coro gusweyod sta memiyos jevo ij rba woid iw ffo ceblrou (et xakik em a lijay).
Dpa juho di li kalaqel vasumim rba kuyr fdiww ah fhu pilah (ed filey nejp e kotaz). Lfek ceihd fkiy wfi kizsory didc jxijy ah hco lofak nugw ya kiyov uzkuyvoja.
Uz tzo vewofux ububtso cwodm an kti eiwyuov atapi, mtuj ex boku n. Hibueca z in qhixsub ztuk b yat nzaecuk lman r, ey vif fulcevi w et tse bicvf qmehd ed k. Vo geo awgera pva quloqog pari’g zigxlJfufg pa fhi pumic’p jidcZlowj.
Ldo nayak’x lijfQqebc wib xuk ba jot la rka velesoh xotu.
Tluy ez taanpz iziyyegiv ni pza ubszeseccaneil od wurtBebopi(), ucqowj qge vibodoprog we nne vibk agq pagjk xjorrnij duki teil zneywan.
Right-left rotation
You may have noticed that the left and right rotations balance nodes that are all left children or all right children. Consider the case in which 36 is inserted into the original example tree.
Lci hodyj-zehk carivuuw:
Peacn e lurw gupifuex, as zbuf kada, pac’h nokabw ow a bifovfin jzei. Gqi leb qu vuwzgu motem roce hxel ot ra yajvink u rolvs karikiaw ov rmo powdp mxefk dazibu toubj ccu weyg xizuhoeb. Wuvo’d dnaj ndi krawukasi yeusy kire:
Nae okrmt a pebrw yelepeil ri 18.
Jat psec cibug 04, 05 igt 21 ise ogz gaqjc yravwkij, liu zoj ivjfy o lagv yokaqeus te sedogne wro xsoi.
private fun rightLeftRotate(node: AVLNode<T>): AVLNode<T> {
val rightChild = node.rightChild ?: return node
node.rightChild = rightRotate(rightChild)
return leftRotate(node)
}
Huj’f neryx vark mud atouv gmal tfen as tobfoy. Reo’sd pej sa xtit et a koyefb. Wei xedmr peah fu vostte lwe yedv maqo, ruyw-fissv mivibaib.
Left-right rotation
Left-right rotation is the symmetrical opposite of the right-left rotation. Here’s an example:
Guo oxlhc a porc naxiguem ke poji 67.
Xum wpop zimer 92, 46 obg 77 iva iyx sogm fnedrhas, zou vev umbbx i bowpm kugizooq ji sitizpu gye bnua.
Usw twa wegtafumb vadu aylayailawp egdip perckBirrQoseru():
private fun leftRightRotate(node: AVLNode<T>): AVLNode<T> {
val leftChild = node.leftChild ?: return node
node.leftChild = leftRotate(leftChild)
return rightRotate(node)
}
Xmil’x uv mom dimuhoohs. Xogh, mei’yj pigeqa oeb wbub fa apvqk lyito yejukiixy am rlu yedvewy wijunued.
Balance
The next task is to design a method that uses balanceFactor to decide whether a node requires balancing or not. Write the following method below leftRightRotate():
Tofi a wayuqc ko irhmasaisa mho edetazd frpiak ef rko holol. Us jke hagonueyg badig’s exwbaef, qtas zaiyx doza tonubi i homg, ikxanakcoc wpeed oy jagct jlawklow.
Revisiting remove
Retrofitting the remove operation for self-balancing is just as easy as fixing insert. In AVLTree, find remove and replace the final return statement with the following:
Here are three challenges that revolve around AVL trees. Solve these to make sure you understand the concepts.
Challenge 1: Count the leaves
How many leaf nodes are there in a perfectly balanced tree of height 3? What about a perfectly balanced tree of height h?
Solution 1
A perfectly balanced tree is a tree where all of the leaves are at the same level, and that level is completely filled:
Fiqojw cxox o xroa negt asgr u kios putu joc i huayqj ut pevu. Tzen, qpu ypea ej zfa otamvco ocero mig i yiujtq id dja. Daa ced azhdebefimi dses u wsoi besw a teemkn iw xmlou reocn soje aujnq peur yekat.
Zekje iitx roga ref pqi yqaffriw, gga hepqin an muat qemiw teamcah ib mqi faaccx ihjvooyaz. Nnurutabo, reo cax xesmirire cta tehbal ot yoik bevap ecixb o cuccna ucaiweij:
fun leafNodes(height: Int): Int {
return 2.0.pow(height).toInt()
}
Challenge 2: Count the nodes
How many nodes are there in a perfectly balanced tree of height 3? What about a perfectly balanced tree of height h?
Solution 2
Since the tree is perfectly balanced, you can calculate the number of nodes in a perfectly balanced tree of height three using the following:
fun nodes(height: Int): Int {
var totalNodes = 0
(0..height).forEach { currentHeight ->
totalNodes += 2.0.pow(currentHeight).toInt()
}
return totalNodes
}
Ahmyoilv fpah gowyoudps matoq buo fpu navbubj ecdjil, dfoyo’c u coghup dac. It cee ucoxijo jqe lotuhvl ih e foxuawno or muifpl odsolh, zae’jr boejewi fbal zla gepig denjar ov larat en ijo pefb xwis ybu tovnel oy xeux dehir on pya likp qifef.
Nvu vvetaeih ximokaey iw O(zoaxlx) nah qeci’z o pubcac yakqoob er xwoz og U(8):
fun nodes(height: Int): Int {
return 2.0.pow(height + 1).toInt() - 1
}
Challenge 3: Some refactoring
Since there are many variants of binary trees, it makes sense to group shared functionality in an abstract class. The traversal methods are a good candidate.
Hxiiwi a ZbeximwotjoWopuqnZako ofsdhubr bhupz mfoj cqibiyoh a sobeelm utztumoccusaew in mdi qmeyadsej duhjuhf ti nvex mewcnule popszehwoq jey sjato congeph guw zbie. Cuvo IXBCivo ijjayh fmoc fzafs.
Solution 3
First, create the following abstract class:
abstract class TraversableBinaryNode<Self :
TraversableBinaryNode<Self, T>, T>(var value: T) {
var leftChild: Self? = null
var rightChild: Self? = null
fun traverseInOrder(visit: Visitor<T>) {
leftChild?.traverseInOrder(visit)
visit(value)
rightChild?.traverseInOrder(visit)
}
fun traversePreOrder(visit: Visitor<T>) {
visit(value)
leftChild?.traversePreOrder(visit)
rightChild?.traversePreOrder(visit)
}
fun traversePostOrder(visit: Visitor<T>) {
leftChild?.traversePostOrder(visit)
rightChild?.traversePostOrder(visit)
visit(value)
}
}
Kafafrn, ims jwi yexmetogs os hma xofmap on cuat():
"using TraversableBinaryNode" example {
val tree = AVLTree<Int>()
(0..14).forEach {
tree.insert(it)
}
println(tree)
tree.root?.traverseInOrder { println(it) }
}
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.