Reviewing HW5 - bst insert
Reviewing HW5 - bst delete
Reviewing HW6 - Rotations, RBTs
An example: ROBDD
Refer to HW5 (pdf)
for the following discussion items.
Consider the bst_insert function that has three bugs.
One error is that if T is NULL, setting T to newT does not change
the binary search tree. Why? Because T is a COPY of B->root, it is
NOT B->root itself. So after we set T = newT, B->root is still NULL.
To correct this, set B->root = newT instead.
Another error relates to the condition given in the while loop.
The condition
T->left != NULL && T->right != NULL
implies that we want to continue searching while the node that T points to
has exactly two children. The reasoning here is wrong since we can insert
on to nodes with one child or nodes with 0 children (leaves). In fact, if
you look at the body of the while loop, the insert code is correctly given
and must eventually be executed since we will see a NULL pointer to the
left or right when we find the correct insert point. Thus, one solution to
this error is to change the condition of the while loop to true.
The third error has to do with the fact that if you find a node with the
same key, you shouldn't set T = newT here since nodes below the matching
node would be lost. Instead, reset the data in T to the new element:
T->data = x;
Note that there is a difference between the key k and the element x (that
contains key k). The tree is ordered based on keys, but the information in
each data field is an element. It is possible that we want to insert an element
with the same key as one already in the tree. For example, in a structure holding
student records, the key might be student ID. If you change your address, we would reinsert your student record with the new information. The insert operation
would find the key already in the structure and replace the old record with
the new record.
Recursion vs. Iteration: You'll note that we used iteration here for
bst_insert which is reasonable since we are only traversing one path down the
tree. If we need to traverse the entire tree, however, iteration becomes more
difficult since we'd have to move down and up the tree, requiring parent
pointers or some auxiliary data structure like a stack to keep track of where
we've been. So for traversing the whole tree, recursion is generally preferred.
Reviewing the tree_delete operation, the main thing to think about
here is to understand that this function takes a pointer to some node in
the binary search tree, T, which we consider to be the local root of a
binary search tree. It could be the actual root (B->root) or it could be
the root of a subtree which is also a BST. This function should return a
pointer to the local root of the same tree once the element with the given
key is deleted.
For most cases, the pointer returned by this function will be the same
pointer it gets as its parameter since the local root of the BST won't be
deleted. But there will be a case where we will need to delete the local
root of a BST. In this situation, the code deals with the various cases
needed to fix the tree and return a pointer to the new local root for
the BST once the appropriate node has been deleted.
If T is NULL, then the tree is empty, so there's nothing to delete, so
we return NULL also. That is, if you give me an empty tree and tell me
to delete something, I return back an empty tree.
If the key is less than the key in the root, then we need to try to delete
the key from the left subtree. Remember that this recursive call will return
back a pointer to the updated tree once the delete happens (if it does).
So we need to reset our left pointer to point to this new tree. Thus,
the code for this is:
T->left = tree_delete(T->left, k);
A similar argument applies when we try to delete the key from the right
subtree.
If the key is in the local root (T->data), then we need to figure out how
to delete it. If the left subtree of T is empty, then using the BST ordering
property, the root of the right subtree can be the root of the updated
tree, so we can return T->right in this case. Likewise, if the right subtree is
empty, then the root of the left subtree can be the root of the updated tree,
so we can return T->left. Of course, if both are NULL, we return NULL since
we're removing a node that is a leaf so the updated tree will be empty.
What if the node we want to delete has two children? We have a choice to look
toward the left or right for a replacement value. In this implementation, we
always look left. If the left child has no right child, then a simple fix is
to copy the data from the left child to the local root and then removing the
left child instead:
But if the left child has two children, then we can't do this since we'll
lose the right subtree of the left child. So using the fact that an inorder
traversal gives the data values in order, we can find the inorder predecessor
of the key to be deleted and copy it to the local root (and then remove
it from its original position). The inorder predecessor of the local root
is simply the largest value in the left subtree of the local root.
So we need the helper function findLargestChild to find this value,
remove it from the tree and return it so we can copy it to node T.
Here is a simple recursive implementation for findLargestChild. Make
sure you understand why it works and why the preconditions are always
satisfied.
In the solution above, we need to "look ahead" to see when the maximum value
is to our right so we can change the parent's pointer to point to the maximum
node's left child instead. (Think about it: is this valid in a BST?)
Refer to HW6 (pdf)
for the following discussion items.
For any arbitrary n-node BST, why are there n-1 possible rotations?
Quite simply, every node can be rotated with its parent except for the
root, since the root has no parent.
In a red-black tree, the length of the longest path from the root to a leaf
is at most twice the length of the shortest path. We can show this using
the structure invariants for RBTs. Since the black height is the same,
the number of black nodes must be the same in both paths. Also, since a
red node can't have a red parent, we can have at most red node between each
pair of black nodes along a path from root to leaf. Thus, the longest path
can be at most twice as long as the shortest path. That's why the red-black
tree remains "balanced"!
Reviewing HW5 - Binary Search Trees - insert
Reviewing HW5 - Binary Search Trees - delete
T->data = T->left->data;
T->left = T->left->left;
elem findLargestChild(tree T)
//@requires T != NULL && T->right != NULL;
{
if (T->right->right == NULL) {
elem x = T->right->data;
T->right = T->right->left; // The largest value can have a left child!
return x;
}
else return findLargestChild(T->right);
}
Reviewing HW6