This section provides exercises for better understanding in balanced binary search trees.
Explain how the remove()
method works in the AbstractBalancedBinarySearchTree
class:
Explain what gets assigned to lowest
in L71
.
Explain how the balance()
methods in AVLTree
and RedBlackTree
keep the trees balanced (or fails to keep them balanced) after a removal.
Write a report including your explanation and save it as quiz4.pdf
.
Create the BalacnedBinarySearchTreeQuiz
class under the tree.balanced
package that inherits theAbstractBalancedBinarySearchTree
class.
Override the balance()
method that first checks the following conditions:
node
is the only child &
node
’s parent is the right child of node
's grand parent &
node
’s uncle has only one child.
If all of the above conditions are satisfied, balance()
makes multiple rotations such that the left subtree is always full before any node gets added to the right subtree.
For the example below, after adding the keys (in red) to the trees 1
, 2
, 3
, and 4
, they all need to be transformed into the tree 5
by the balance()
method.
The transform must be performed only by rotations; other setter methods such as setLeftChild()
or setRightChild()
are not allowed in this assignment.
This section discusses a type of balanced binary search trees called AVL Trees.
AVL (Adelson-Velsky and Landis) trees preserve the balance at all time by keeping track of the height of every node through a balance factor.
Let us create the class inheriting AbstractBinaryNode
:
L2
: keeps track of the height of this node.
L6
: a node with no children has the height of 1
.
We then override the setLeftChild()
and setRigthChild()
methods such that the height of this node gets adjusted accordingly with the new child by calling the resetHeights()
method:
L15
: adjusts the height of this node and its ancestors, recursively.
L20-22
: retrieve the height of this node.
L24-27
: resets the height of this node if different and makes the recursive call to its parent.
What are the heights of
3
,5
, and7
in the figure below when the height of4
changes from 3 to 4?
Finally, we define the getBalanceFactor()
method that returns the height difference between the left-subtree and the right-subtree of this node:
How can we tell if the node is unbalanced by using the balance factor?
L10,16
: resets the height of node
after rotation.
node.resetHeights()
does not reset heights of nodes in its subtree. Would this cause an issue?
We then override the balance()
method:
L6
: the left-subtree is longer.
L9-10
: the case of left zig-zag.
L12
: the case of left linear.
L14
: the right-subtree is longer.
L17
: the case of right zig-zag.
L20
: the case of right linear.
The followings demonstrate how the balance()
method works in AVL Trees:
What is the worst-case complexity of the
balance()
method inAVLTree
?
Let us create the class that inherits AbstractBalancedBinarySearchTree
and override the createNote()
, rotateLeft()
, and rotateRight()
methods:
This chapter discusses binary search trees using different balancing algorithms.
This section discusses abstraction of binary search trees.
This section assumes that you have already learned core concepts about binary search tress from the prerequisite. Thus, it focuses on the abstraction that can be applied to other types of binary search trees introduced in the following sections.
Let us create an abstract class, AbstractBinaryNode
:
L1
: defines two generic types, T
for the type of the key and N
is for the type of the binary node.
L8
: calls the setKey()
method to assign the value of key
in L2
.
Let us define boolean methods for the member fields:
What is the input parameter
node
isnull
for theisLeftChild()
andisRightChild()
methods?
Let us then define getters to access the member fields:
We can also define helper methods inferred by the getters:
L9
: this
needs to be casted to N
since the input parameter of isLeftChild()
is N
.
Is it safe to downcast
this
toN
?
Finally, let us define setters and their helper methods:
L5,10
: sets node
to be a child of this
in two steps:
L6,11
: replaces the parent of node
with this
.
L7,12
: sets node
to be the child.
L20
: replaces the parent of node
with this
in two steps:
L22
: node
gets abandoned by its current parent.
L23
: this
becomes the new parent of node
.
L32
: replaces oldChild
of this node with newChild
.
Let us define the BinaryNode
class inheriting AbstractBinaryNode
:
L1
: defines only 1 generic type T
for the comparable key and passes itself for the generic type N
to theAbstractBinaryNode
class.
Is there any abstract method from
AbstractBinaryNode
to be defined inBinaryNode
?
Let us define an abstract class, AbstractBinarySearchTree
:
L1
: defines two generic types, T
for the type of the key and N
is for the type of the binary node.
L4
: initializes the member field root
to null
.
L9
: creates a binary node typed N
, required for the add()
method.
Why does Java not allow a generic type to be instantiated (e.g.,
node = new N()
)?
Let us define searching methods:
What are the worst-case complexities of
findNode()
,findMinNode()
, andfindMaxNode()
?
Let us define the add()
method:
L5
: creates a node with the key to be the root if this tree does not include any node.
L7
: finds the appropriate location for the key and creates the node.
What does the
add()
method above do when the input key already exists in the tree?
Let us define the remove()
method:
L6
: removes a node with two children using the Hibbard algorithm.
L7
: removes a node with no or one child
The removeSelf()
method makes the node's only child as the child of its parent and removes it:
L6
: finds the child of node
.
L7
: replaces node
with its child.
The removeHibbard()
method finds a node that can be the parent of the left- and the right-children of node
and makes it a child of its parent:
Which nodes are guaranteed to be the parent of those left- and right- children?
The following demonstrates how the above removeHibbard()
method works:
What is the worst-case complexity of the
removeHibbard()
method?
Let us define the BinarySearchTree
inheriting AbstractBinarySearchTree
:
This section discusses a type of balanced binary search trees called Red-Black Trees.
Red-Black trees preserve the balance at most time by satisfying the following conditions:
Every node is either red or black.
The root and all leaves (null
) are black.
Every red node must have two black children.
Every path from a given node to any of its leaves goes through the same number of black nodes.
Let us create the RedBlackNode
class inheriting AbstractBinaryNode
:
L2
: if is_red
is true
, the color of this node is red; otherwise, it is black.
L6
: the color of the node is black by default upon instantiation.
Let us the RedBlackTree
class inheriting AbstractBalancedBinarySearchTree
:
Finally, let us override balance()
method that handles 3 cases:
L3
: sets the color to black if node
is the root.
L5
: if the color of node
's parent is red:
L8
: checks if the color of node
's uncle is also red.
L10
: the color of node
's uncle is black.
What about the case when the color of
node
's parent is black?
The following shows a method that handles when both the parent and the uncle are red:
Are there any structural changes after this method is called?
The following shows a method that handles when the parent is red but the uncle is black:
L7
: the case of left zig-zag.
L11
: the case of right zig-zag.
L19
: the case of left linear.
L21
: the case of right linear.
The followings demonstrate how the balance()
method works in Red-Black trees:
How does a Red-Black tree know when it is unbalanced?
This section discusses abstraction of binary search trees.
A binary search tree gives the worst-case complexity of for searching a key when it is not balanced, where is the number of nodes in the tree:
Search
Insert
Delete
Unbalanced
Balanced
To ensure the complexity, it needs to be balanced at all time (or at most time).
There are 4 cases of unbalanced trees to be considered:
Left linear [3, 2, 1]
Right linear [1, 2, 3]
Left zig-zag [3, 1, 2]
Right zig-zag [1, 3, 2]
These cases can be balanced by performing rotations as follows:
What should happen to the subtrees (red/orange/green/blue triangles) after the rotations?
Let us create an abstract class AbstractBalancedBinarySearchTree
that inherits the abstract class AbstractBinarySearchTree
:
Where does
node
end up being after the rotations?
The followings demonstrate how the rotateLeft()
method works:
Let us override the add()
and remove()
methods that call the abstract method balance()
:
L3
: calls the add()
method in the super class, AbstractBinarySearchTree
.
L4
: performs balancing on node
that is just added.
L14
: performs balancing on node
that is the lowest node in the tree affected by this removal.
L24
: defines a balancing algorithm for a specific type of balanced binary search trees.
What are the worst-case complexities of the
add()
andremove()
methods above?