(* 15-150, Spring 2024 *)
(* Michael Erdmann & Dilsun Kaynar *)
(* Code for Lecture 18: *)
(* Representation Invariants in the context of Red/Black Trees. *)
(************************************************************************)
signature DICT =
sig
type key = string (* concrete type *)
type 'a entry = key * 'a (* concrete type *)
type 'a dict (* abstract type *)
val empty : 'a dict
val lookup : 'a dict -> key -> 'a option
val insert : 'a dict * 'a entry -> 'a dict
end
structure RedBlackTree :> DICT =
struct
type key = string
type 'a entry = string * 'a
datatype 'a dict =
Empty (* considered black *)
| Red of 'a dict * 'a entry * 'a dict
| Black of 'a dict * 'a entry * 'a dict
(* Abstraction Function:
The collection of entries in the binary tree constitutes
the dictionary.
*)
(* Red/Black Tree Representation (RBT) Invariants:
1. The tree is sorted, with no duplicate keys:
for every node Red(left, (key1,value1), right)
and every node Black(left, (key1,value1), right),
every key in left is LESS than key1
and every key in right is GREATER than key1.
2. The children of a red node are black.
3. Every path from the root to a leaf has the same number of
black nodes, called the black height of the tree.
*)
(* We also define an Almost Red/Black Tree (ARBT)
by the following invariants:
1. The tree is sorted, just like a RBT.
2. The children of a red node are black except possibly
at the root: the root and one of its children may
both be red.
3. The black height is well-defined, just like in a RBT.
*)
val empty = Empty
(* lookup : 'a dict * key -> 'a option
REQUIRES: d is a RBT.
ENSURES: (lookup d k) returns SOME(v), if d contains an entry
of the form (k,v);
NONE, otherwise.
*)
fun lookup d key =
let
fun lk (Empty) = NONE
| lk (Red tree) = lk' tree
| lk (Black tree) = lk' tree
and lk' (left, (key1,value1), right) =
(case String.compare(key,key1) of
EQUAL => SOME(value1)
| LESS => lk left
| GREATER => lk right)
in
lk d
end
(* restoreLeft : 'a dict -> 'a dict
REQUIRES: Either d is a RBT
or d's root is black, its left child is an ARBT,
and its right child a RBT.
ENSURES: restoreLeft(d) is a RBT,
containing exactly the same entries as d, and
with the same black height as d.
*)
fun restoreLeft(Black(Red(Red(d1, x, d2), y, d3), z, d4)) =
Red(Black(d1, x, d2), y, Black(d3, z, d4))
| restoreLeft(Black(Red(d1, x, Red(d2, y, d3)), z, d4)) =
Red(Black(d1, x, d2), y, Black(d3, z, d4))
| restoreLeft d = d
(* restoreRight : 'a dict -> 'a dict
REQUIRES: Either d is a RBT
or d's root is black, its left child is a RBT,
and its right child an ARBT.
ENSURES: restoreRight(d) is a RBT,
containing exactly the same entries as d, and
with the same black height as d.
*)
fun restoreRight(Black(d1, x, Red(d2, y, Red(d3, z, d4)))) =
Red(Black(d1, x, d2), y, Black(d3, z, d4))
| restoreRight(Black(d1, x, Red(Red(d2, y, d3), z, d4))) =
Red(Black(d1, x, d2), y, Black(d3, z, d4))
| restoreRight d = d
(* insert : 'a dict * 'a entry -> 'a dict
REQUIRES: d is a RBT.
ENSURES: insert(d,e) is a RBT containing exactly all the
entries of d plus e, with e replacing an entry of d
if the keys are EQUAL.
The locally defined helper function ins satisfies:
ins : 'a dict -> 'a dict
REQUIRES: d is a RBT.
ENSURES: ins(d) is a tree containing exactly all the entries
of d plus e, with e replacing an entry of d if the
keys are EQUAL.
ins(d) has the same black height as d.
Moreover, if d's root is black, then ins(d) is a RBT;
if d's root is red, then ins(d) is an ARBT.
*)
fun insert (d, entry as (key, datum)) =
let
fun ins (Empty) = Red(Empty, entry, Empty)
| ins (Red(left, entry1 as (key1,_), right)) =
(case String.compare (key, key1) of
EQUAL => Red(left, entry, right)
| LESS => Red(ins left, entry1, right)
| GREATER => Red(left, entry1, ins right))
| ins (Black(left, entry1 as (key1,_), right)) =
(case String.compare (key, key1) of
EQUAL => Black(left, entry, right)
| LESS => restoreLeft(Black(ins left, entry1, right))
| GREATER => restoreRight(Black(left, entry1, ins right)))
in
(case ins d of
Red (t as (Red _, _, _)) => Black t (* recolor *)
| Red (t as (_, _, Red _)) => Black t (* recolor *)
| d' => d')
end
(* It would also have been fine to implement that case as follow:
(case ins d of
Red t => Black t
| d' => d')
This version always recolors a Red node,
even if there is no Red-Red violation.
*)
end (* structure RedBlackTree *)
(* Some examples and tests: *)
structure RBTTests =
struct
structure RBT = RedBlackTree
val r0 = RBT.empty
val r1 = RBT.insert (r0, ("a",1))
val r2 = RBT.insert (r1, ("b",2))
val r3 = RBT.insert (r2, ("c",3))
val r4 = RBT.insert (r3, ("d",4))
val r5 = RBT.insert (r4, ("e",5))
val look5 = RBT.lookup r5
val SOME(5) = look5 "e"
val SOME(1) = look5 "a"
val NONE = look5 "f"
end
(************************************************************************)
(* The implementation given above is fairly straightforward, but
it is possible to reduce the number of rotations that
might be required from O(log n) to 2 by looking more carefully
at the color of the tree d4.
[The total asymptotic complexity of an insertion continues to
be O(log n): Finding the location at which to insert the new
element may take time O(log n) and there may be O(log n) recolorings
of nodes as a result, but the number of rotations required is at most 2.
Moreover, the recolorings actually also have constant amortized time!
So only the search for the insertion point is truly O(log n).]
Thanks to Paul Zagieboylo for pointing this out and providing this code:
fun restoreLeft (Black (z, Red (y, Red (x, d1, d2), d3), Red d4)) =
Red (z, Black (y, Red (x, d1, d2), d3), Black d4)
| restoreLeft (Black (z, Red (y, Red (x, d1, d2), d3), d4)) =
Black (y, Red (x, d1, d2), Red (z, d3, d4))
| restoreLeft (Black (z, Red (x, d1, Red (y, d2, d3)), Red d4)) =
Red (z, Black (x, d1, Red (y, d2, d3)), Black d4))
| restoreLeft (Black (z, Red (x, d1, Red (y, d2, d3)), d4)) =
Black (y, Red (x, d1, d2), Red (z, d3, d4))
| restoreLeft d = d
restoreRight is symmetrically similar.
Wikipedia has some pictures showing rotations and recolorings:
http://en.wikipedia.org/wiki/Red-black_tree
*)
(************************************************************************)