After I tried to implement BST in Go, it seems like I want to modify the BST to AVL because BST is not a fairly optimal tree data structure.
When I said this:
To find a specific node you don’t have to go around the whole tree, you need to know that BST can route to a specific node by checking the node value
It’s half true because there’s a case that BST makes a linear tree like this:
Linear Tree
And if you want to find a node with value 6, in the end, you will travel the whole tree. That’s why we need AVL to improve the time complexity. AVL will try to rebalance the tree whenever it becomes imbalance after insertion/deletion.
The whole concept of AVL is much the same with BST besides the rebalancing algorithm. In AVL we need to rebalance the tree by rotating every imbalance sub-tree in every insertion/deletion. So we’re gonna use all the code from here and modified it a bit.
To see the tree is balanced or not, we need to define the height on each node. We can calculate the height by counting the maximum height of the left and the right node recursively. If the node has no child, it means its height is 1 otherwise we compare the maximum height of the children.
Update the node struct by adding the height attribute, add the Getter function, and set the value to 1 inside the constructor.
And to keep track of the height and the balance of the tree after insertion/deletion, we need to have a updateHeight and balanceFactor function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func(n*node)balanceFactor()int{ifn==nil{return0}returnn.left.Height()-n.right.Height()}funcmax(a,bint)int{ifa>b{returna}returnb}func(n*node)updateHeight(){// compare the maximum height of the children + its own heightn.height=max(n.left.Height(),n.right.Height())+1}
balanceFactor function determines whether the tree is heavier on the left or the right side. If it returns an integer below 0, it means it’s heavier on the right side and we need to rotate to the left side of the tree. The thresholds for imbalanced tree are -1 and 1, so if the balanceFactor function returns less then -1 or greater than 1, we need to rotate the tree.
Now let’s create the rotate function. There are 2 types of rotate, rotateLeft and rotateRight. But there are 4 conditions to rotate the tree on insertion and deletion. You can read it and see the picture from here.
funcrotateInsert(node*node,valint)*node{// update the height on every insertionnode.updateHeight()// bFactor will tell you which side the weight is onbFactor:=node.balanceFactor()// linearly to the leftifbFactor>1&&val<node.left.value{returnrightRotate(node)}// linearly to the rightifbFactor<-1&&val>node.right.value{returnleftRotate(node)}// less than symbolifbFactor>1&&val>node.left.value{node.left=leftRotate(node.left)returnrightRotate(node)}// greater than symbolifbFactor<-1&&val<node.right.value{node.right=rightRotate(node.right)returnleftRotate(node)}returnnode}
Lastly, you need to update the return statement of the insertNode function.
functraverse(node*node){// exit conditionifnode==nil{return}fmt.Println(node.value)traverse(node.left)traverse(node.right)}funcmain(){tree:=avl.New()// to check if your implementation is correct// First insert this sequentially// to the AVL Visualiztion Pagetree.Insert(0)tree.Insert(1)tree.Insert(2)tree.Insert(3)tree.Insert(4)tree.Insert(5)tree.Insert(6)tree.Insert(7)// Second insert Traverse function results sequentially// to the BST Visualization pagetree.Traverse()// 3 1 0 2 5 4 6 7}
If you find the tree visualizations are the same and balanced, then it’s correct.
funcrotateDelete(node*node)*node{node.updateHeight()bFactor:=node.balanceFactor()// linearly to the leftifbFactor>1&&node.left.balanceFactor()>=0{returnrightRotate(node)}// less than symbolifbFactor>1&&node.left.balanceFactor()<0{node.left=leftRotate(node.left)returnrightRotate(node)}// linearly to the rightifbFactor<-1&&node.right.balanceFactor()<=0{returnleftRotate(node)}// greater than symbolifbFactor<-1&&node.right.balanceFactor()>0{node.right=rightRotate(node.right)returnleftRotate(node)}returnnode}
Deletion is not like insertion in that we can compare the entered values, because the node we are looking for is already deleted. That’s why we need to compare the current node’s balance factor with the balance factor of the child. Now, you need to modify the removeNode function. Remember when removing a node with 2 children, we need to find the successor and there are 2 ways to find the successor.
Find the least valueable node from the right child of the node
Find the greatest valueable node from the left child of the node
We used the first way while the BST & AVL Visualization Page using the second way. You can also change the code so it’s easy to visualize.
funcgreatest(node*node)*node{ifnode==nil{returnnil}ifnode.right==nil{returnnode}returngreatest(node.right)}funcremoveNode(node*node,valint)(*node,error){ifnode==nil{returnnil,ErrNodeNotFound}ifval>node.value{right,err:=removeNode(node.right,val)iferr!=nil{returnnil,err}node.right=right}elseifval<node.value{left,err:=removeNode(node.left,val)iferr!=nil{returnnil,err}node.left=left}else{ifnode.left!=nil&&node.right!=nil{// has 2 children// find the successorsuccessor:=greatest(node.left)value:=successor.value// remove the successorleft,err:=removeNode(node.left,value)iferr!=nil{returnnil,err}node.left=left// copy the successor value to the current nodenode.value=value}elseifnode.left!=nil||node.right!=nil{// has 1 child// move the child position to the current nodeifnode.left!=nil{node=node.left}else{node=node.right}}elseifnode.left==nil&&node.right==nil{// has no child// simply remove the nodenode=nil}}ifnode==nil{returnnil,nil}returnrotateDelete(node),nil}
You can validate and recheck your AVL implementation with the BST & AVL visualization page.
packageavlimport("errors""fmt")var(ErrDuplicatedNodeerror=errors.New("bst: found duplicated value on tree")ErrNodeNotFounderror=errors.New("bst: node not found"))typenodestruct{height,valueintleft,right*node}func(n*node)balanceFactor()int{ifn==nil{return0}returnn.left.Height()-n.right.Height()}func(n*node)updateHeight(){max:=func(a,bint)int{ifa>b{returna}returnb}n.height=max(n.left.Height(),n.right.Height())+1}func(n*node)Height()int{ifn==nil{return0}returnn.height}func(n*node)Value()int{returnn.value}func(n*node)Left()*node{returnn.left}func(n*node)Right()*node{returnn.right}funcnewNode(valint)*node{return&node{height:1,value:val,left:nil,right:nil,}}funcinsertNode(node*node,valint)(*node,error){// if there's no node, create oneifnode==nil{returnnewNode(val),nil}// if there's duplicated node returns errorifnode.value==val{returnnil,ErrDuplicatedNode}// if value is greater than current node's value, insert to the rightifval>node.value{right,err:=insertNode(node.right,val)iferr!=nil{returnnil,err}node.right=right}// if value is less than current node's value, insert to the leftifval<node.value{left,err:=insertNode(node.left,val)iferr!=nil{returnnil,err}node.left=left}returnrotateInsert(node,val),nil}funcremoveNode(node*node,valint)(*node,error){ifnode==nil{returnnil,ErrNodeNotFound}ifval>node.value{right,err:=removeNode(node.right,val)iferr!=nil{returnnil,err}node.right=right}elseifval<node.value{left,err:=removeNode(node.left,val)iferr!=nil{returnnil,err}node.left=left}else{ifnode.left!=nil&&node.right!=nil{// has 2 children// find the successorsuccessor:=greatest(node.left)value:=successor.value// remove the successorleft,err:=removeNode(node.left,value)iferr!=nil{returnnil,err}node.left=left// copy the successor value to the current nodenode.value=value}elseifnode.left!=nil||node.right!=nil{// has 1 child// move the child position to the current nodeifnode.left!=nil{node=node.left}else{node=node.right}}elseifnode.left==nil&&node.right==nil{// has no child// simply remove the nodenode=nil}}ifnode==nil{returnnil,nil}returnrotateDelete(node),nil}funcfindNode(node*node,valint)*node{ifnode==nil{returnnil}// if the node is found, return the nodeifnode.value==val{returnnode}// if value is greater than current node's value, search recursively to the rightifval>node.value{returnfindNode(node.right,val)}// if value is less than current node's value, search recursively to the leftifval<node.value{returnfindNode(node.left,val)}returnnil}funcrotateInsert(node*node,valint)*node{// update the height on every insertionnode.updateHeight()// bFactor will tell you which side the weight is onbFactor:=node.balanceFactor()// linearly to the leftifbFactor>1&&val<node.left.value{returnrightRotate(node)}// linearly to the rightifbFactor<-1&&val>node.right.value{returnleftRotate(node)}// less than symbolifbFactor>1&&val>node.left.value{node.left=leftRotate(node.left)returnrightRotate(node)}// greater than symbolifbFactor<-1&&val<node.right.value{node.right=rightRotate(node.right)returnleftRotate(node)}returnnode}funcrotateDelete(node*node)*node{node.updateHeight()bFactor:=node.balanceFactor()// linearly to the leftifbFactor>1&&node.left.balanceFactor()>=0{returnrightRotate(node)}// less than symbolifbFactor>1&&node.left.balanceFactor()<0{node.left=leftRotate(node.left)returnrightRotate(node)}// linearly to the rightifbFactor<-1&&node.right.balanceFactor()<=0{returnleftRotate(node)}// greater than symbolifbFactor<-1&&node.right.balanceFactor()>0{node.right=rightRotate(node.right)returnleftRotate(node)}returnnode}funcrightRotate(x*node)*node{y:=x.leftt:=y.righty.right=xx.left=tx.updateHeight()y.updateHeight()returny}funcleftRotate(x*node)*node{y:=x.rightt:=y.lefty.left=xx.right=tx.updateHeight()y.updateHeight()returny}funcgreatest(node*node)*node{ifnode==nil{returnnil}ifnode.right==nil{returnnode}returngreatest(node.right)}functraverse(node*node){// exit conditionifnode==nil{return}fmt.Println(node.value)traverse(node.left)traverse(node.right)}funcmax(a,bint)int{ifa>b{returna}returnb}
Thank you for reading!
···
Love This Content?
Any kind of supports is greatly appreciated! Kindly support me via Bitcoin, Ko-fi, Trakteer, or just continue to read another content. You can write a response via Webmention and let me know the URL via Telegraph.