CS 361: Solutions for Homework #6


Problem 1. Prove formally that every AVL tree is a red-black tree -- i.e., that every AVL tree can have its nodes colored in red or black such that (i) the root and the leaves are black; (ii) every red node has a black parent; and (iii) the number of black nodes is the same on each path from the root to a leaf.

The first thing to note is that property (iii) is not local -- we cannot enforce it by using only local information. (Two subtrees in different parts of the overall tree may have the same height, but if one is completely full -- every node in even balance -- then it could end up colored entirely black while the other, leaning as much as possible in one direction, requires a lot of red nodes and ends up with much shorter black paths. We need to be able to predict what "black height" each subtree needs. The balance factors may help in that, but are not sufficient: they are also a type of local information, for one subtree only.

Thus we need to make a first traversal of the tree to obtain some global information. A crucial piece of information is the distance to the closest leaf from each node, as that puts an upper bound on the "black height" of the subtree rooted at that node. We can obtain that information in one tree traversal and store it within each tree node.

  function comp_paths(p: tree_node): integer;
    begin
      if p == nil
         then comp_path = 0
         else begin
                p^.min = 1+min(comp_paths(p^.left),comp_paths(p^.right));
                comp_paths = p^.min
              end
    end;
From the root, we can now do a preorder traversal, carrying along the desired height (counting only black nodes) of the subtree about to be entered. Initially, that height is simply the value root^.min and the root is colored black. As the recursive traversal enters, with a desired height of h, some node p, we begin by checking if p^.min exceeds h---in which case we shall still need red nodes within the subtree rooted at \verb%p%. If p^.min > h, then we check whether p can be colored red -- i.e., whether p parent is already red; if p can be colored red, we color it red and leave h unchanged, else we color it black and decrease h by 1. The recursion now proceeds on both children with the new value of h. If p^.min = h (it cannot be smaller), then all nodes on the shortest path to a leaf, including p itself, must be black, so we color p black, decrease h, and proceed with the recursion with the new value of h.
   procedure rec_color(p: tree_node; h: integer);
     begin
       if p == nil then return;
       if (p^.min > h) and ((p^.parent)^.color == black)
          then p^.color = red;
          else p^.color = black; h = h-1
       endif;
       rec_color(p^.left,h);
       rec_color(p^.right,h)
     end;

   comp_paths(root);
   h = root^.min;
   rec_color(root,h)
This procedure explicitly colors the root black and explicitly ensures that a red node has a black parent. So, it remains to show that the leaves are colored black and that the number of black nodes on any path from the root to a leaf is constant. The procedure ensures the latter by carrying a desired height for the subtree entered next in the recursion, a height measured in the number of black nodes along the path; that value is set at the root and maintained in the traversal, so every path from the root to a leaf has that same number of black nodes. It ensures the former (that every leaf is colored black) by coloring every node black whenever the shortest path from that node to a leaf equals the desired height, which is true at every leaf. That the desired height never exceeds the length of a shortest path to a leaf (i.e., that we never have h > p^.min) is due to the assumption that the tree is an AVL tree (in fact, as mentioned in class, it would be sufficient to assume that the length of a longest path never exceeds twice that of a shortest path), together with our setting the initial h to the initial p^.min.

We could equally well use the longest path from a node to a leaf as the basis of an algorithm, setting the desired number of black nodes to half that value. Whereas the algorithm described above uses as few red nodes as possible, basing the coloring on the longest path to a leaf ends up using as many red nodes as possible.



Problem 2. Solve the Dutch national flag problem. You are given an array filled with elements that can take one of only 3 values: red, white, and blue. The goal is to return the array with all red elements on the left, all blue elements on the right, and all white elements in the middle. Your algorithm must run in linear time, can only exchange elements, and has just one additional variable to hold one of the elements -- in particular, it cannot use counters to count the number of elements of each color.

On the other hand, the program can use several pointers. The reason for the limitation on storage is to avoid the trivial strategy of traversing the array once to collect information (how many blue and how many white elements), then using distribution sort directly. Instead, we want to exchange elements as needed. Basically, we modify the exchange idea of quicksort, treating red and blue as below and above the threshold and white as equal to the threshold and forming a third region. When the two "chariots" moving from the left and the right ends of the array stop at a blue on the left and a red on the right, we just exchange the two elements as in quicksort and keep moving the chariots. Our invariant on these two chariots (pointers) will be that everything to the left of the left (red) chariot is red and everything to the right of the right (blue) chariot is blue. The presence of white elements, however, forces us to use a third (white) chariot; that chariot starts in the same position as the red chariot and moves to the right as long as it sees white elements; it will maintain the invariant that every element between it and the red chariot is white. When the white chariot hits a non-white element; it exchanges that element with a white element hit by the proper colored chariot, depending on the color of the non-white element. Thus we have exchanges between the red and the blue chariot of colored elements only and exchanges between the white and another chariot involving one colored and one white element. The general situation looks like this:

left           red chariot =>  white chariot =>   <= blue chariot     right
 \ /               \ /             \ /                    \ /          \ /
  |                 |               |                      |            |
  |------ A --------|------- B -----|--------- C ----------|----- D ----|
The invariants are that everything in zone A is red, everything in zone B is white, and everything in zone D is blue, while elements in zone C can be in any mix. Initially, zones A, B, and D are empty, and zone C is the entire array. At the end, the white chariot and the blue chariot stop against each other, reducing zone C to nothing; the invariants then assert that every element from the left to the red chariot is red, every element from the red chariot to the white chariot is white, and every element from the white chariot to the right is blue, as desired.

There is no restriction on which chariot we choose to move next, when a choice exists; we simply must ensure that the red chariot never passes the white chariot and test for a meeting of the white and the blue chariots.

Each chariot moves in only one direction, so the running time is clearly linear. Each colored element is exchanged at most once -- the colored element in an exchange is always one that is on the "wrong end" of the array and is sent to the "other end" through the exchange, where it will stay. A white element may end up being exchanged more than once, but, since every exchange involves at least one colored element and since each colored element is involved in at most one exchange, the total number of exchanges is less than or equal to the number of colored elements.