Red Black Search Trees - data-structures

I'm implementing a red-black search tree as described Introduction To Algorithms 3rd Edition.
I have a problem where the tree doesn't get properly recolored after deleting a node with no children. On page 324 is described the RB-DELETE function. If a node is a leaf node, it's child pointers point to a special null node.
What I don't understand is, that when this function is run on a leaf node, it assigns the null node to x, then replaces the node with the subtree of null, thus altering the parent pointer of null. In the end, it runs the fixup method on null.
Shouldn't editing the null node be illegal? And what am I not understanding, why doesn't it work?
Before delete of 2:
After delete of 2:

Looking at the two PNGs you posted something doesn't add up.
In your picture, a number of things have changed after deleting 2. The root has been change from 5 to 7 and 7 went from red to black.
The original.. leaf (null) node's aren't shown.
5(B)
3(R) 7(R)
2(B) 4(B) 6(B) 9(B)
8(R) 11(R)
When you delete node 2:
The sub-tree goes from...
3(R)
/ \
2(B) 4(B)
to...
3(R)
/ \
null(B) 4(B)
After removing 2:
5(B)
3(R) 7(R)
4(B) 6(B) 9(B)
8(R) 11(R)
Case 4 of delete: Sibling and Sibling's children are black, but Parent is red. In this case, we simply exchange the colors of Sibling (to red) and Parent (to black).
After re-coloring sibling and parent:
5(B)
3(B) 7(R)
4(R) 6(B) 9(B)
8(R) 11(R)
As you can see, simply removing a node with two leaf-node children is a simple re-coloring and doesn't change the shape of the tree.

Related

Something strange about Red-Black Tree Delete in Introduction to Algorithms

In the Introduction to Algorithms 3rd edition, page 329, Figure 13.7 shows us about the 4 deletion cases.
(source: quoracdn.net)
But I have a problem: in that figure, all the node of x is BLACK and it's not nil. But I have tested many cases and it turns out that x must be either a RED node or nil, for the reason:
If the z has less then 2 children, then x must be a RED node or nil because z's black height must be 1.
If the z has 2 children, the x must be a RED node or nil because y is the successor of z and y's left child must be nil, then y's black height must be 1.
Am I wrong? Or is there anything that I has ignored? Hope someone can help me.
Thanks for your time.
What is z? It is not marked on any of the 4 cases. Do you mean w the sibling node?
To my mind, x is either null (and that is valid, you have just deleted it) or it is black and the node you have deleted is further down either x's left child tree or x's right child tree.
Note also before the FIXUP routine is called, various other cases are called.
If the node to deleted has 0 children and is Red => just delete it. It is a leaf node. It is still a red-black tree.
If the node to deleted has 1 child => The node is black and child red. No other possibilities can occur as they break the Red-Black tree rules. Delete the node and replace with the Child. The Child is now black. It is still a Red-Black tree.
If the node to be deleted has 2 children => Find the inorder Successor or Predecessor. Swap nodes to be deleted BUT NOT THE COLOURS. The inorder Successor or Predecessor will always be a node with 0 or 1 children. Now you just need to delete that which reduces to the 2 cases above.
So what is left to do?
The difficult case: 0 children and the node is Black.
In the diagrams above, case 2 is the start case and the sibling left and right children are nil. x no longer exists but w does and is black
To my mind, you need to find out in your code why x is red. It should never be red. If it is, the preamble code before the fixup code is not working and the fixup code is only called when the node to be deleted is black and with no children.

Inserting into Augmented Red Black Tree

I am looking at the code of inserting into an augmented Red black tree. This tree has an additional field called "size" and it keeps the size of a subtree rooted at a node x. Here is the pseudocode for inserting a new node:
AugmentedRBT_Insert(T,x){
BST_Insert(T,x); //insert as if it is a normal BST
x[color]=red; //insert as a red node
size[x]=1;
tmp=parent[x];
while(tmp!=NULL){ //start from the node x and follow the path to root
size[tmp]=size[tmp]+1; //update the size of each node
tmp=parent[tmp];
}
}
Forget about fixing the coloring and rotations, they will be done in another function. My question is, why do we set the size of the newly added node "x" to 1? I understand that it will not have any subtrees, so its size must be 1, but one of the requirements of RBT is that every red node has two black children, in fact every leaf node is NULL and even if we insert the node "x" as black, it still should have 2 black NULL nodes and i think we must set its size to 3? Am i wrong?
Thanks.
An insertion in a red-black tree, as in most binary trees, happens directly at a leaf. Hence the size of the subtree rooted at the leaf is 1. The red node does have two black children, because leaves always have the "root" or "nil" as a child, which is black. Those null elements aren't nodes, so we wouldn't count them.
Then, we go and adjust the sizes of all parents up to the root (they each get +1 for the node we just added).
Finally, we fix these values when we rotate the tree to balance it, if necessary. In your implementation, you will probably want to do both the size updates and rotations in one pass instead of two.

Inserting into red black tree

I am taking an algorithms course and in my course slides, there is an example of insertion into a red-black tree:
My question is, why don't we let "2" be a leaf node here? It looks like if we let it be a leaf node, then no condition of a red black tree is violated. What am I missing here?
The Problem is not with the position of 2 the the second tree of your image but the color of different nodes. Here is the explanation:
1st Rule of insertion in Red-Black tree is: the newly inserted node has to be always Red. You fall in case 3 insertion where both the father and uncle of node 2 is Red. So they are needed to be recolored to Black, and the grandfather will become Red but as the grandfather is root so it will become Black again.
So the new tree (after inserting 2) should be like this (r and b indicate color, .b is Nil node):
5b
/ \
1b 7b
/ \ / \
.b 2r .b .b
/ \
.b .b
And why we always need to insert red node in RBT, you may ask? Answer is, 1st we know every NIL nodes are always Black in RBT, 2nd we have rule 5. Every simple path from a given node to any of its descendant leaves contains the same number of black nodes. Now if we insert a black node at the end the tree will violate this rule, just put 2b in above tree instead of 2r and keep color of 1 and 7 red, then count black node from root to any Nil node, you will see some path have 2 back nodes and some path have 3 black nodes.
All the leaves of a Red Black tree have to be NIL
Check property 3
The wikipedia article, based on the same idea, explains it as follow:
In many of the presentations of tree data structures, it is possible for a node to have only one child, and leaf nodes contain data. It is possible to present red–black trees in this paradigm, but it changes several of the properties and complicates the algorithms. For this reason, this article uses "null leaves",
So clearly nothing prevents you to do it your way, but you have to take it in account in your algorithms, which make them significantly more complex. Perhaps this issue can be somewhat alleviated by using OOP, where leaves contain elements, but behave as nodes with empty leaves.
Anyway, it's a trade off: what you would gain in space (roughly two pointers set to NULL in C), you'd probably lose in code complexity, computation time, or in the object runtime representation (specialized methods for the leaves).
Black-height not uniform.
If you count the number of blacks nodes searching NIL nodes from root, 5-1-2-nil has three and 5-7-nil or 5-1-nil only two.
(rule: Every path from a given node to any of its descendant NIL nodes contains the same number of black nodes)

Deletion procedure for a Binary Search Tree

Consider the deletion procedure on a BST, when the node to delete has two children. Let's say i always replace it with the node holding the minimum key in its right subtree.
The question is: is this procedure commutative? That is, deleting x and then y has the same result than deleting first y and then x?
I think the answer is no, but i can't find a counterexample, nor figure out any valid reasoning.
EDIT:
Maybe i've got to be clearer.
Consider the transplant(node x, node y) procedure: it replace x with y (and its subtree).
So, if i want to delete a node (say x) which has two children i replace it with the node holding the minimum key in its right subtree:
y = minimum(x.right)
transplant(y, y.right) // extracts the minimum (it doesn't have left child)
y.right = x.right
y.left = x.left
transplant(x,y)
The question was how to prove the procedure above is not commutative.
Deletion (in general) is not commutative. Here is a counterexample:
4
/ \
3 7
/
6
What if we delete 4 and then 3?
When we delete 4, we get 6 as the new root:
6
/ \
3 7
Deleting 3 doesn't change the tree, but gives us this:
6
\
7
What if we delete 3 and then 4?
When we delete 3 the tree doesn't change:
4
\
7
/
6
However, when we now delete 4, the new root becomes 7:
7
/
6
The two resulting trees are not the same, therefore deletion is not commutative.
UPDATE
I didn't read the restriction that this is when you always delete a node with 2 children. My solution is for the general case. I'll update it if/when I can find a counter-example.
ANOTHER UPDATE
I don't have concrete proof, but I'm going to hazard a guess:
In the general case, you handle deletions differently based on whether you have two children, one child, or no children. In the counter-example I provided, I first delete a node with two children and then a node with one child. After that, I delete a node with no children and then another node with one child.
In the special case of only deleting nodes with two children, you want to consider the case where both nodes are in the same sub-tree (since it wouldn't matter if they are in different sub-trees; you can be sure that the overall structure won't change based on the order of deletion). What you really need to prove is whether the order of deletion of nodes in the same sub-tree, where each node has two children, matters.
Consider two nodes A and B where A is an ancestor of B. Then you can further refine the question to be:
Is deletion commutative when you are considering the deletion of two nodes from a Binary Search Tree which have a ancestor-descendant relationship to each other (this would imply that they are in the same sub-tree)?
When you delete a node (let's say A), you traverse the right sub-tree to find the minimum element. This node will be a leaf node and can never be equal to B (because B has two children and cannot be a leaf node). You would then replace the value of A with the value of this leaf-node. What this means is that the only structural change to the tree is the replacement of A's value with the value of the leaf-node, and the loss of the leaf-node.
The same process is involved for B. That is, you replace the value of the node and replace a leaf-node. So in general, when you delete a node with two children, the only structural change is the change in value of the node you are deleting, and the deletion of the leaf node who's value you are using as replacement.
So the question is further refined:
Can you guarantee that you will always get the same replacement node regardless of the order of deletion (when you are always deleting a node with two children)?
The answer (I think) is yes. Why? Here are a few observations:
Let's say you delete the descendant node first and the ancestor node second. The sub-tree that was modified when you deleted the descendant node is not in the left sub-tree of the ancestor node's right child. This means that this sub-tree remains unaffected. What this also means is regardless of the order of deletion, two different sub-trees are modified and therefore the operation is commutative.
Again, let's say you delete the descendant node first and the ancestor node second. The sub-tree that was modified when you deleted the descendant node is in the left sub-tree of the ancestor node's right child. But even here, there is no overlap. The reason is when you delete the descendant node first, you look at the left sub-tree of the descendant node's right child. When you then delete the ancestor node, you will never go down that sub-tree since you will always be going towards the left after you enter the ancestor node's right-child's left sub-tree. So again, regardless of what you delete first you are modifying different sub-trees and so it appears order doesn't matter.
Another case is if you delete the ancestor node first and you find that the minimum node is a child of the descendant node. This means that the descendant node will end up with one child, and deleting the one child is trivial. Now consider the case where in this scenario, you deleted the descendant node first. Then you would replace the value of the descendant node with its right child and then delete the right child. Then when you delete the ancestor node, you end up finding the same minimum node (the old deleted node's left child, which is also the replaced node's left child). Either way, you end up with the same structure.
This is not a rigorous proof; these are just some observations I've made. By all means, feel free to poke holes!
It seems to me that the counterexample shown in Vivin's answer is the sole case of non-commutativity, and that it is indeed eliminated by the restriction that only nodes with two children can be deleted.
But it can also be eliminated if we discard what appears to be one of Vivin's premises, which is that we should traverse the right subtree as little as possible to find any acceptable successor. If, instead, we always promote the smallest node in the right subtree as the successor, regardless of how far away it turns out to be located, then even if we relax the restriction on deleting nodes with fewer than two children, Vivin's result
7
/
6
is never reached if we start at
4
/ \
3 7
/
6
Instead, we would first delete 3 (without successor) and then delete 4 (with 6 as successor), yielding
6
\
7
which is the same as if the order of deletion were reversed.
Deletion would then be commutative, and I think it is always commutative, given the premise I have named (successor is always smallest node in right subtree of deleted node).
I do not have a formal proof to offer, merely an enumeration of cases:
If the two nodes to be deleted are in different subtrees, then deletion of one does not affect the other. Only when they are in the same path can the order of deletion possibly affect the outcome.
So any effect on commutativity can come only when an ancestor node and one of its descendants are both deleted. Now, how does their vertical relationship affect commutativity?
Descendant in the left subtree of the ancestor. This situation will not affect commutativity because the successor comes from the right subtree and cannot affect the left subtree at all.
Descendant in the right subtree of the ancestor. If the ancestor's successor is always the smallest node in the right subtree, then order of deletion cannot change the choice of successor, no matter what descendant is deleted before or after the ancestor. Even if the successor to the ancestor turns out to be the descendant node that is also to be deleted, that descendant too is replaced with the the next-largest node to it, and that descendant cannot have its own left subtree remaining to be dealt with. So deletion of an ancestor and any right-subtree descendant will always be commutative.
I think there are two equally viable ways to delete a node, when it has 2 children: SKIP TO CASE 4...
Case 1: delete 3 (Leaf node)
2 3
/ \ --> / \
1 3 1
Case 2: delete 2 (Left child node)
2 3
/ \ --> / \
1 3 1
Case 3: delete 2 (Right child node)
2 2
/ \ --> / \
1 3 3
______________________________________________________________________
Case 4: delete 2 (Left & Right child nodes)
2 2 3
/ \ --> / \ or / \
1 3 1 3
BOTH WORK and have different resulting trees :)
______________________________________________________________________
As algorithm explained here: http://www.mathcs.emory.edu/~cheung/Courses/323/Syllabus/Trees/AVL-delete.html
Deleting a node with 2 children nodes:
1) Replace the (to-delete) node with its in-order predecessor or in-order successor
2) Then delete the in-order predecessor or in-order successor
I respond here to Vivin's second update.
I think this is a good recast of the question:
Is deletion commutative when you are
considering the deletion of two nodes
from a Binary Search Tree which have a
ancestor-descendant relationship to
each other (this would imply that they
are in the same sub-tree)?
but this bold sentence below is not true:
When you delete a node (let's say A),
you traverse the right sub-tree to
find the minimum element. This node
will be a leaf node and can never be equal to B
since the minimum element in A's right subtree can have a right child. So, it is not a leaf.
Let's call the minimum element in A's right subtree successor(A).
Now, it is true that B cannot be successor(A), but it can be in its right subtree. So, it is a mess.
I try to summarize.
Hypothesis:
A and B have two children each.
A and B are in the same subtree.
Other stuff we can deduce from hypothesis:
B is not successor(A), neither A is successor(B).
Now, given that, i think there are 4 different cases (as usual, let be A an ancestor of B):
B is in A's left subtree
B is an ancestor of successor(A)
successor(A) is an ancestor of B
B and successor(A) don't have any relationship. (they are in different A's subtrees)
I think (but of course i cannot prove it) that cases 1, 2 and 4 don't matter.
So, only in the case successor(A) is an ancestor of B deletion procedure could not be commutative. Or could it?
I pass the ball : )
Regards.

Binary Search Tree - node deletion

I am trying to understand why when deleting a node in a BST tree and having to keep the children and adhering to the BST structure, you have to either take the node's right child (higher value, then node being deleted) and if that right child has a left child take that child. Else just the the node being deleted right child.
Why don't you just take the node being deleted left child, if there's one. It still works out correctly?
Or have I missed something.
I'm reading this article.
You're oversimplifying.
The node selected to replace the one that was deleted must be larger than all the nodes to the left of the deleted one, and smaller than all the nodes to the right. So it must be either the left subtree's rightmost descendant or the right subtree's leftmost descendant; except if one or the other subtree is entirely absent, we can remove a level of tree entirely simply by replacing the deleted node with the child that was present.
The rules listed in the article will always give you the right subtree's leftmost descendant when both trees are present. If you wished, you could indeed derive an alternative ruleset that used the leftmost subtree's rightmost descendant instead.
It does not "work out correctly" to just always use the left child. Indeed, if there is a child on the right and the left child itself has two children, it cannot even be done without essentially rebuilding the tree.
You would be correct for the special case that you described. But for something more general where you can have many more levels deeper than the node being deleted you need to replace that node with a node that will be less than everything to the right, and greater than everything to the left. So as an example:
2
/ \
1 6
/ \
4 7
\
5
Let's say you wanted to move the node 6, now following your instructions we will replace it with the left child, node 4. Now what do we do with node 5? We could make it the left child of node 7 (or the left most descendant of node 7 if it existed), but why would you do all this reshuffling when you know that removing a leaf is trivial and you just want to replace the node with another node that would keep every node on the left less and every node on the right greater.

Resources