RedblackImplementation and Proof of Red-Black Trees
Required Reading
- Section 3.3 of Algorithms, Fourth Edition, by Sedgewick and Wayne, Addison Wesley 2011; or
- Chapter 13 of Introduction to Algorithms, 3rd Edition, by Cormen, Leiserson, and Rivest, MIT Press 2009; or
- Wikipedia.
Implementation
From VFA Require Import Perm.
From VFA Require Import Extract.
From Coq Require Import Lists.List.
Export ListNotations.
From Coq Require Import Logic.FunctionalExtensionality.
Require Import ZArith.
Open Scope Z_scope.
Definition key := int.
Inductive color := Red | Black.
Section TREES.
Variable V : Type.
Variable default: V.
Inductive tree : Type :=
| E : tree
| T: color → tree → key → V → tree → tree.
Definition empty_tree := E.
From VFA Require Import Extract.
From Coq Require Import Lists.List.
Export ListNotations.
From Coq Require Import Logic.FunctionalExtensionality.
Require Import ZArith.
Open Scope Z_scope.
Definition key := int.
Inductive color := Red | Black.
Section TREES.
Variable V : Type.
Variable default: V.
Inductive tree : Type :=
| E : tree
| T: color → tree → key → V → tree → tree.
Definition empty_tree := E.
lookup is exactly as in our (unbalanced) search-tree algorithm
in Extract, except that the T constructor carries a color
component, which we can ignore here.
Fixpoint lookup (x: key) (t : tree) : V :=
match t with
| E ⇒ default
| T _ tl k v tr ⇒ if ltb x k then lookup x tl
else if ltb k x then lookup x tr
else v
end.
match t with
| E ⇒ default
| T _ tl k v tr ⇒ if ltb x k then lookup x tl
else if ltb k x then lookup x tr
else v
end.
The balance function is copied directly from Okasaki's paper.
Now, the nice thing about machine-checked proof in Coq is that you
can prove this correct without actually understanding it! So, do
read Okasaki's paper, but don't worry too much about the details
of this balance function.
Definition balance rb t1 k vk t2 :=
match rb with Red ⇒ T Red t1 k vk t2
| _ ⇒
match t1 with
| T Red (T Red a x vx b) y vy c ⇒
T Red (T Black a x vx b) y vy (T Black c k vk t2)
| T Red a x vx (T Red b y vy c) ⇒
T Red (T Black a x vx b) y vy (T Black c k vk t2)
| a ⇒ match t2 with
| T Red (T Red b y vy c) z vz d ⇒
T Red (T Black t1 k vk b) y vy (T Black c z vz d)
| T Red b y vy (T Red c z vz d) ⇒
T Red (T Black t1 k vk b) y vy (T Black c z vz d)
| _ ⇒ T Black t1 k vk t2
end
end
end.
match rb with Red ⇒ T Red t1 k vk t2
| _ ⇒
match t1 with
| T Red (T Red a x vx b) y vy c ⇒
T Red (T Black a x vx b) y vy (T Black c k vk t2)
| T Red a x vx (T Red b y vy c) ⇒
T Red (T Black a x vx b) y vy (T Black c k vk t2)
| a ⇒ match t2 with
| T Red (T Red b y vy c) z vz d ⇒
T Red (T Black t1 k vk b) y vy (T Black c z vz d)
| T Red b y vy (T Red c z vz d) ⇒
T Red (T Black t1 k vk b) y vy (T Black c z vz d)
| _ ⇒ T Black t1 k vk t2
end
end
end.
(In contrast, Sedgewick has proposed left-leaning red-black
trees, which have a simpler balance function (but a more
complicated invariant). He does this in order to make the proof of
correctness easier: there are fewer cases in the balance
function, and therefore fewer cases in the case-analysis of the
proof of correctness of balance. But as you will see, our proofs
about balance will have automated case analyses, so we don't
care how many cases there are!)
The insert operation uses a helper function ins to do
the insertion, and another function makeBlack to color
the resulting tree's root black.
Definition makeBlack t :=
match t with
| E ⇒ E
| T _ a x vx b ⇒ T Black a x vx b
end.
Fixpoint ins x vx s :=
match s with
| E ⇒ T Red E x vx E
| T c a y vy b ⇒ if ltb x y then balance c (ins x vx a) y vy b
else if ltb y x then balance c a y vy (ins x vx b)
else T c a x vx b
end.
Definition insert x vx s := makeBlack (ins x vx s).
match t with
| E ⇒ E
| T _ a x vx b ⇒ T Black a x vx b
end.
Fixpoint ins x vx s :=
match s with
| E ⇒ T Red E x vx E
| T c a y vy b ⇒ if ltb x y then balance c (ins x vx a) y vy b
else if ltb y x then balance c a y vy (ins x vx b)
else T c a x vx b
end.
Definition insert x vx s := makeBlack (ins x vx s).
Verification
- SearchTree: Red-black trees should satisfy the SearchTree
representation invariant: the keys in each left subtree are all
less than the node's key, and the keys in each right subtree are
greater.
- Abs: Red-black trees should be correct w.r.t. the abstraction
relation Abs, which relates trees to maps.
- Balanced: For sake of performance, red-black trees should be balanced. Specifically, they should satisfy the red-black invarinats: there is the same number of black nodes on any path from the root to each leaf; and there are never two consecutive red nodes.
Proof Automation for Case-Analysis Proofs.
Lemma T_neq_E:
∀c l k v r, T c l k v r ≠ E.
Lemma ins_not_E: ∀x vx s, ins x vx s ≠ E.
Proof.
intros. destruct s; simpl.
apply T_neq_E.
remember (ins x vx s1) as a1.
unfold balance.
∀c l k v r, T c l k v r ≠ E.
unfold not. intros. discriminate.
Qed.
Qed.
Lemma ins_not_E: ∀x vx s, ins x vx s ≠ E.
Proof.
intros. destruct s; simpl.
apply T_neq_E.
remember (ins x vx s1) as a1.
unfold balance.
Let's destruct on the topmost case, ltb x k. We can use
destruct instead of bdestruct because we don't need to
remember whether x<k or x≥k.
destruct (ltb x k).
The topmost test is match c with..., so just destruct c.
destruct c.
apply T_neq_E.
apply T_neq_E.
The topmost test is match a1 with..., so just destruct a1.
destruct a1.
The topmost test is match s2 with..., so just destruct s2.
destruct s2.
unfold not. intros. discriminate.
unfold not. intros. discriminate.
How long will this go on? A long time! But it will terminate. Just
keep typing. Better yet, let's automate.
The following tactic applies whenever the current goal looks like
match ?c with Red ⇒ _ | Black ⇒ _ end ≠ _
and what it does in that case is, destruct c.
match goal with
| ⊢ match ?c with Red ⇒ _ | Black ⇒ _ end ≠ _ ⇒ destruct c
end.
| ⊢ match ?c with Red ⇒ _ | Black ⇒ _ end ≠ _ ⇒ destruct c
end.
The following tactic applies whenever the current goal looks like,
match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end ≠ _
and what it does in that case is, destruct s.
match goal with
| ⊢ match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end ≠ _ ⇒ destruct s
end.
| ⊢ match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end ≠ _ ⇒ destruct s
end.
Let's apply that tactic again, and then try it on the subgoals,
recursively. Recall that the repeat tactical keeps trying the
same tactic on subgoals.
repeat match goal with
| ⊢ match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end ≠ _ ⇒ destruct s
end.
match goal with
| ⊢ T _ _ _ _ _ ≠ E ⇒ apply T_neq_E
end.
| ⊢ match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end ≠ _ ⇒ destruct s
end.
match goal with
| ⊢ T _ _ _ _ _ ≠ E ⇒ apply T_neq_E
end.
Using what we've learned, let's start the proof all over again.
Abort.
Lemma ins_not_E: ∀x vx s, ins x vx s ≠ E.
Proof.
intros. destruct s; simpl.
apply T_neq_E.
remember (ins x vx s1) as a1.
unfold balance.
Lemma ins_not_E: ∀x vx s, ins x vx s ≠ E.
Proof.
intros. destruct s; simpl.
apply T_neq_E.
remember (ins x vx s1) as a1.
unfold balance.
This is the beginning of the big case analysis. This time, let's
combine several tactics together:
repeat match goal with
| ⊢ (if ?x then _ else _) ≠ _ ⇒ destruct x
| ⊢ match ?c with Red ⇒ _ | Black ⇒ _ end ≠ _⇒ destruct c
| ⊢ match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end ≠ _ ⇒ destruct s
end.
| ⊢ (if ?x then _ else _) ≠ _ ⇒ destruct x
| ⊢ match ?c with Red ⇒ _ | Black ⇒ _ end ≠ _⇒ destruct c
| ⊢ match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end ≠ _ ⇒ destruct s
end.
What we have left is 117 cases, every one of which can be proved
the same way:
apply T_neq_E.
apply T_neq_E.
apply T_neq_E.
apply T_neq_E.
apply T_neq_E.
apply T_neq_E.
(* Only 111 cases to go... *)
Abort.
apply T_neq_E.
apply T_neq_E.
apply T_neq_E.
apply T_neq_E.
apply T_neq_E.
(* Only 111 cases to go... *)
Abort.
Let's start over again, adding that apply to the case analysis.
Lemma ins_not_E: ∀x vx s, ins x vx s ≠ E.
Proof.
intros. destruct s; simpl.
apply T_neq_E.
remember (ins x vx s1) as a1.
unfold balance.
repeat match goal with
| ⊢ (if ?x then _ else _) ≠ _ ⇒ destruct x
| ⊢ match ?c with Red ⇒ _ | Black ⇒ _ end ≠ _ ⇒ destruct c
| ⊢ match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end ≠ _ ⇒ destruct s
| ⊢ T _ _ _ _ _ ≠ E ⇒ apply T_neq_E
end.
Qed.
Proof.
intros. destruct s; simpl.
apply T_neq_E.
remember (ins x vx s1) as a1.
unfold balance.
repeat match goal with
| ⊢ (if ?x then _ else _) ≠ _ ⇒ destruct x
| ⊢ match ?c with Red ⇒ _ | Black ⇒ _ end ≠ _ ⇒ destruct c
| ⊢ match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end ≠ _ ⇒ destruct s
| ⊢ T _ _ _ _ _ ≠ E ⇒ apply T_neq_E
end.
Qed.
Success! We'll use that same technique of repeatedly matching
against the goal in a few more proofs, below.
The SearchTree property for red-black trees is exactly the same as
for (unbalanced) search trees; we just ignore the color c of each
node.
Verifying Preservation of the Representation Invariant
Inductive SearchTree' : Z → tree → Z → Prop :=
| ST_E : ∀lo hi, lo ≤ hi → SearchTree' lo E hi
| ST_T : ∀lo c l k v r hi,
SearchTree' lo l (int2Z k) →
SearchTree' (int2Z k + 1) r hi →
SearchTree' lo (T c l k v r) hi.
Inductive SearchTree: tree → Prop :=
| ST_intro: ∀t lo hi, SearchTree' lo t hi → SearchTree t.
| ST_E : ∀lo hi, lo ≤ hi → SearchTree' lo E hi
| ST_T : ∀lo c l k v r hi,
SearchTree' lo l (int2Z k) →
SearchTree' (int2Z k + 1) r hi →
SearchTree' lo (T c l k v r) hi.
Inductive SearchTree: tree → Prop :=
| ST_intro: ∀t lo hi, SearchTree' lo t hi → SearchTree t.
Now we want to prove that the red-black operations preserve the
representation invariant. Since lookup doesn't return a tree,
all we need to worry about is empty_tree and insert.
The empty tree is easy:
Empty and SearchTree
Theorem empty_SearchTree : SearchTree empty_tree.
Proof.
unfold empty_tree. apply ST_intro with (lo := 0) (hi := 0).
constructor. omega.
Qed.
Proof.
unfold empty_tree. apply ST_intro with (lo := 0) (hi := 0).
constructor. omega.
Qed.
Verifying that insert preserves SearchTree will take more
work.
Since the balance function is an important part of insert,
let's start by proving that balance produces a search tree
whose bounds are related to the bounds on its inputs:
Balance and SearchTree
Lemma balance_SearchTree:
∀c s1 k kv s2 lo hi,
SearchTree' lo s1 (int2Z k) →
SearchTree' (int2Z k + 1) s2 hi →
SearchTree' lo (balance c s1 k kv s2) hi.
Proof.
intros.
unfold balance.
∀c s1 k kv s2 lo hi,
SearchTree' lo s1 (int2Z k) →
SearchTree' (int2Z k + 1) s2 hi →
SearchTree' lo (balance c s1 k kv s2) hi.
Proof.
intros.
unfold balance.
Use proof automation for this case analysis.
repeat match goal with
| ⊢ SearchTree' _ (match ?c with Red ⇒ _ | Black ⇒ _ end) _ ⇒ destruct c
| ⊢ SearchTree' _ (match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end) _ ⇒ destruct s
end.
| ⊢ SearchTree' _ (match ?c with Red ⇒ _ | Black ⇒ _ end) _ ⇒ destruct c
| ⊢ SearchTree' _ (match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end) _ ⇒ destruct s
end.
58 cases to consider!
* constructor; auto.
* constructor; auto.
* constructor; auto.
(* There's a pattern emerging, but, let's keep going for now. *)
* constructor; auto.
constructor; auto. constructor; auto.
* constructor; auto.
* constructor; auto.
(* There's a pattern emerging, but, let's keep going for now. *)
* constructor; auto.
constructor; auto. constructor; auto.
To prove this one, we have to do inversion on the proof goals
above the line.
inv H. inv H0. inv H8. inv H9.
auto.
constructor; auto.
inv H. inv H0. inv H8. inv H9. auto.
inv H. inv H0. inv H8. inv H9. auto.
auto.
constructor; auto.
inv H. inv H0. inv H8. inv H9. auto.
inv H. inv H0. inv H8. inv H9. auto.
There's a pattern here. Whenever we have a hypothesis above the line
that looks like,
- H: SearchTree' _ E _ - H: SearchTree' _ (T _ ) _
Abort.
Lemma balance_SearchTree:
∀c s1 k kv s2 lo hi,
SearchTree' lo s1 (int2Z k) →
SearchTree' (int2Z k + 1) s2 hi →
SearchTree' lo (balance c s1 k kv s2) hi.
Proof.
intros.
unfold balance.
Lemma balance_SearchTree:
∀c s1 k kv s2 lo hi,
SearchTree' lo s1 (int2Z k) →
SearchTree' (int2Z k + 1) s2 hi →
SearchTree' lo (balance c s1 k kv s2) hi.
Proof.
intros.
unfold balance.
Use proof automation for this case analysis.
repeat match goal with
| ⊢ SearchTree' _ (match ?c with Red ⇒ _ | Black ⇒ _ end) _ ⇒
destruct c
| ⊢ SearchTree' _ (match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end) _ ⇒
destruct s
| H: SearchTree' _ E _ ⊢ _ ⇒ inv H
| H: SearchTree' _ (T _ _ _ _ _) _ ⊢ _ ⇒ inv H
end.
| ⊢ SearchTree' _ (match ?c with Red ⇒ _ | Black ⇒ _ end) _ ⇒
destruct c
| ⊢ SearchTree' _ (match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end) _ ⇒
destruct s
| H: SearchTree' _ E _ ⊢ _ ⇒ inv H
| H: SearchTree' _ (T _ _ _ _ _) _ ⊢ _ ⇒ inv H
end.
58 cases to consider!
* constructor; auto.
* constructor; auto. constructor; auto. constructor; auto.
* constructor; auto. constructor; auto. constructor; auto.
constructor; auto. constructor; auto.
* constructor; auto. constructor; auto. constructor; auto.
constructor; auto. constructor; auto.
* constructor; auto. constructor; auto. constructor; auto.
constructor; auto. constructor; auto.
* constructor; auto. constructor; auto. constructor; auto.
* constructor; auto. constructor; auto. constructor; auto.
constructor; auto. constructor; auto.
* constructor; auto. constructor; auto. constructor; auto.
constructor; auto. constructor; auto.
* constructor; auto. constructor; auto. constructor; auto.
constructor; auto. constructor; auto.
Do we see a pattern here? We can add that to our automation!
Abort.
Lemma balance_SearchTree:
∀c s1 k kv s2 lo hi,
SearchTree' lo s1 (int2Z k) →
SearchTree' (int2Z k + 1) s2 hi →
SearchTree' lo (balance c s1 k kv s2) hi.
Proof.
intros.
unfold balance.
Lemma balance_SearchTree:
∀c s1 k kv s2 lo hi,
SearchTree' lo s1 (int2Z k) →
SearchTree' (int2Z k + 1) s2 hi →
SearchTree' lo (balance c s1 k kv s2) hi.
Proof.
intros.
unfold balance.
Use proof automation for this case analysis.
repeat match goal with
| ⊢ SearchTree' _ (match ?c with Red ⇒ _ | Black ⇒ _ end) _ ⇒
destruct c
| ⊢ SearchTree' _ (match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end) _ ⇒
destruct s
| H: SearchTree' _ E _ ⊢ _ ⇒ inv H
| H: SearchTree' _ (T _ _ _ _ _) _ ⊢ _ ⇒ inv H
end;
repeat (constructor; auto).
Qed.
| ⊢ SearchTree' _ (match ?c with Red ⇒ _ | Black ⇒ _ end) _ ⇒
destruct c
| ⊢ SearchTree' _ (match ?s with E ⇒ _ | T _ _ _ _ _ ⇒ _ end) _ ⇒
destruct s
| H: SearchTree' _ E _ ⊢ _ ⇒ inv H
| H: SearchTree' _ (T _ _ _ _ _) _ ⊢ _ ⇒ inv H
end;
repeat (constructor; auto).
Qed.
Exercise: 2 stars, standard (ins_SearchTree)
You won't need proof automation of the kind we just used for balance_SearchTree to prove this one. You will need to apply balance_SearchTree twice. If you solved insert_SearchTree in SearchTree you could copy and adapt that proof.
Lemma ins_SearchTree: ∀x vx s lo hi,
lo ≤ int2Z x →
int2Z x < hi →
SearchTree' lo s hi →
SearchTree' lo (ins x vx s) hi.
Proof.
(* FILL IN HERE *) Admitted.
☐
lo ≤ int2Z x →
int2Z x < hi →
SearchTree' lo s hi →
SearchTree' lo (ins x vx s) hi.
Proof.
(* FILL IN HERE *) Admitted.
Insert and SearchTree
Search Z.le.
Search Z.min.
Search Z.max.
Search Z.min.
Search Z.max.
Lemma ins_E_SearchTree : ∀x vx,
SearchTree (ins x vx E).
Proof. (* FILL IN HERE *) Admitted.
☐
SearchTree (ins x vx E).
Proof. (* FILL IN HERE *) Admitted.
Lemma expand_range_SearchTree':
∀s lo hi,
SearchTree' lo s hi →
∀lo' hi',
lo' ≤ lo → hi ≤ hi' →
SearchTree' lo' s hi'.
Proof. (* FILL IN HERE *) Admitted.
Lemma ins_T_SearchTree_aux : ∀x vx c l k kv r lo hi,
SearchTree' lo l (int2Z k)
→ SearchTree' (int2Z k + 1) r hi
→ SearchTree' (Z.min lo (int2Z x))
(ins x vx (T c l k kv r))
(Z.max hi (int2Z x + 1)).
Proof.
Hint: This is the hardest lemma. You'll need two applications
of expand_range_SearchTree', and a few lemmas about Z.
(* FILL IN HERE *) Admitted.
Lemma ins_T_SearchTree : ∀x vx c l k kv r,
SearchTree (T c l k kv r) → SearchTree (ins x vx (T c l k kv r)).
Proof.
Lemma ins_T_SearchTree : ∀x vx c l k kv r,
SearchTree (T c l k kv r) → SearchTree (ins x vx (T c l k kv r)).
Proof.
Hint: the only lemma you need is ins_T_SearchTree_aux.
(* FILL IN HERE *) Admitted.
☐
Lemma ins_makeBlack_SearchTree : ∀s,
SearchTree s → SearchTree (makeBlack s).
Proof. (* FILL IN HERE *) Admitted.
Theorem insert_SearchTree: ∀x vx s,
SearchTree s → SearchTree (insert x vx s).
Proof.
Hint: use ins_makeBlack_SearchTree, ins_E_SearchTree, and
ins_T_SearchTree.
(* FILL IN HERE *) Admitted.
☐
Check empty_SearchTree.
Check insert_SearchTree.
Check insert_SearchTree.
Verifying Correctness w.r.t. the Abstraction Relation
Import IntMaps.
The abstraction relation Abs is the same as what we previously
used, extended to include the color of nodes.
Definition combine {A} (pivot: Z) (m1 m2: total_map A) : total_map A :=
fun x ⇒ if Z.ltb x pivot then m1 x else m2 x.
Inductive Abs : tree → total_map V → Prop :=
| Abs_E : Abs E (t_empty default)
| Abs_T : ∀a b c l k vk r,
Abs l a →
Abs r b →
Abs (T c l k vk r) (t_update (combine (int2Z k) a b) (int2Z k) vk).
fun x ⇒ if Z.ltb x pivot then m1 x else m2 x.
Inductive Abs : tree → total_map V → Prop :=
| Abs_E : Abs E (t_empty default)
| Abs_T : ∀a b c l k vk r,
Abs l a →
Abs r b →
Abs (T c l k vk r) (t_update (combine (int2Z k) a b) (int2Z k) vk).
Empty and Abs
Exercise: 1 star, standard (empty_tree_relate)
Theorem empty_tree_relate :
Abs empty_tree (t_empty default).
Proof. (* FILL IN HERE *) Admitted.
☐
Abs empty_tree (t_empty default).
Proof. (* FILL IN HERE *) Admitted.
Lookup and Abs
Exercise: 1 star, standard (lookup_relate)
Copy and adapt your proof from Extract.
Theorem lookup_relate : ∀k t m,
Abs t m → lookup k t = m (int2Z k).
Proof.
(* FILL IN HERE *) Admitted.
☐
Abs t m → lookup k t = m (int2Z k).
Proof.
(* FILL IN HERE *) Admitted.
Balance and Abs
Lemma Abs_helper:
∀m' t m, Abs t m' → m' = m → Abs t m.
Proof.
intros. subst. auto.
Qed.
Lemma SearchTree'_le:
∀lo t hi, SearchTree' lo t hi → lo ≤ hi.
Proof.
induction 1; omega.
Qed.
∀m' t m, Abs t m' → m' = m → Abs t m.
Proof.
intros. subst. auto.
Qed.
Lemma SearchTree'_le:
∀lo t hi, SearchTree' lo t hi → lo ≤ hi.
Proof.
induction 1; omega.
Qed.
We'll also make use of this Ltac tactic in balance_relate. The
purpose of this tactic is to prove the equivalence of maps.
Ltac contents_equivalent_prover :=
extensionality x; unfold t_update, combine, t_empty;
repeat match goal with
| ⊢ context [if ?A then _ else _] ⇒ bdestruct A
end;
auto;
omega.
extensionality x; unfold t_update, combine, t_empty;
repeat match goal with
| ⊢ context [if ?A then _ else _] ⇒ bdestruct A
end;
auto;
omega.
Here's an example of how that tactic could be used. You might want
to stop and convince yourself that you understand why the tactic
succeeds in proving the example.
Section ContentsExample.
Open Scope string_scope.
Example maps_equiv_ex :
t_update (t_update (t_empty "") 2 "two") 1 "one"
=
t_update (t_update (t_empty "") 1 "one") 2 "two".
Proof.
contents_equivalent_prover.
Qed.
End ContentsExample.
Open Scope string_scope.
Example maps_equiv_ex :
t_update (t_update (t_empty "") 2 "two") 1 "one"
=
t_update (t_update (t_empty "") 1 "one") 2 "two".
Proof.
contents_equivalent_prover.
Qed.
End ContentsExample.
Exercise: 4 stars, standard (balance_relate)
Theorem balance_relate:
∀c l k vk r m,
SearchTree (T c l k vk r) →
Abs (T c l k vk r) m →
Abs (balance c l k vk r) m.
Proof.
intros. inv H. unfold balance.
∀c l k vk r m,
SearchTree (T c l k vk r) →
Abs (T c l k vk r) m →
Abs (balance c l k vk r) m.
Proof.
intros. inv H. unfold balance.
This is the main "loop" of the proof.
repeat
match goal with
| _ ⇒ idtac (* this is just a "no op" to get us started *)
end.
match goal with
| _ ⇒ idtac (* this is just a "no op" to get us started *)
end.
At this point you should have just 1 subgoal. Now you're going to
augment that main loop by adding the following clauses, one at a
time, to it.
- 1. The subgoal has the form
- 2. The first subgoal is provable from the assumptions. So, add a
new clause to the main loop below the clause you just added:
- 3. The subgoal has the form
- 4. The first subgoal has the form Abs (T ...) m. That is, we
want to show that the tree abstracts to map m. Look at the
hypotheses: one of them (call it H0) shows that a similar
tree abstracts to the same m. To make progress, we can
extract the evidence for why that similar tree abstracts to
m. So, add a new clause that inverts the hypothesis using the
inv tactic.
- 5. There is yet more information to be gained about the maps. Look
at the hypotheses for the current subgoal: one of them should
be of the form Abs E .... That is, it tells us something
about a map being the abstraction of the empty tree. Empty
trees were not covered by the clause we just added in step (4).
So, add another clause to invert such hypotheses about the
abstraction of empty trees. You will still have 21 subgoals,
but now none of the hypotheses about Abs will contain
information about the structure of the tree; that information
has all been extracted.
- 6. At this point every subgoal has the form Abs (T ...) (t_update
...). We'd like to apply the Abs_T constructor to make
progress. But the map (t_update ...) does not have the right
form. We therefore need to replace (t_update ...) with
another map that has the right form. That's why we created the
Abs_helper lemma above.
apply Abs_T. apply Abs_T.
apply Abs_E. apply Abs_E.
apply Abs_T. eassumption. eassumption.
repeat econstructor; eassumption.
| ⊢ _ ⇒ eapply Abs_helper; [repeat econstructor; eassumption | ] - 7. The whole reason we created the contents_equivalent_prover
tactic above was to prove the equality of maps. So add this
clause to your main loop:
| ⊢ _ = _ ⇒ contents_equivalent_prover
- 8. The final piece of information to extract for map equalities has to do with an additional property of SearchTree': whenever SearchTree' lo _ hi holds, it also holds that lo ≤ hi. So, add a clause to the main loop to apply SearchTree'_le in any such hypothesis when the subgoal has the form _ = _ .
(* FILL IN HERE *) Admitted.
☐
Insert and Abs
Exercise: 2 stars, standard (ins_left_and_right)
Lemma ins_left : ∀c l j u r k v,
SearchTree (T c l j u r)
→ int2Z k < int2Z j
→ SearchTree (T c (ins k v l) j u r).
Proof. (* FILL IN HERE *) Admitted.
Lemma ins_right : ∀c l j u r k v,
SearchTree (T c l j u r)
→ int2Z j < int2Z k
→ SearchTree (T c l j u (ins k v r)).
Proof. (* FILL IN HERE *) Admitted.
☐
SearchTree (T c l j u r)
→ int2Z k < int2Z j
→ SearchTree (T c (ins k v l) j u r).
Proof. (* FILL IN HERE *) Admitted.
Lemma ins_right : ∀c l j u r k v,
SearchTree (T c l j u r)
→ int2Z j < int2Z k
→ SearchTree (T c l j u (ins k v r)).
Proof. (* FILL IN HERE *) Admitted.
Exercise: 3 stars, standard (ins_relate)
Now we prove the correctness of ins, which is the core of the insert operation.
Theorem ins_relate:
∀k v t m,
SearchTree t →
Abs t m →
Abs (ins k v t) (t_update m (int2Z k) v).
Proof.
(* FILL IN HERE *) Admitted.
☐
∀k v t m,
SearchTree t →
Abs t m →
Abs (ins k v t) (t_update m (int2Z k) v).
Proof.
(* FILL IN HERE *) Admitted.
Lemma makeBlack_relate:
∀t m,
Abs t m →
Abs (makeBlack t) m.
Proof.
intros.
destruct t; simpl; auto.
inv H; constructor; auto.
Qed.
Theorem insert_relate:
∀k v t m,
SearchTree t →
Abs t m →
Abs (insert k v t) (t_update m (int2Z k) v).
Proof.
intros.
unfold insert.
apply makeBlack_relate.
apply ins_relate; auto.
Qed.
∀t m,
Abs t m →
Abs (makeBlack t) m.
Proof.
intros.
destruct t; simpl; auto.
inv H; constructor; auto.
Qed.
Theorem insert_relate:
∀k v t m,
SearchTree t →
Abs t m →
Abs (insert k v t) (t_update m (int2Z k) v).
Proof.
intros.
unfold insert.
apply makeBlack_relate.
apply ins_relate; auto.
Qed.
OK, we're almost done! We have proved all these main theorems:
Check empty_tree_relate.
Check lookup_relate.
Check insert_relate.
Check lookup_relate.
Check insert_relate.
Together these imply that this implementation of red-black trees
is correct, in that it respects the abstraction relation.
Optional: Verification of Elements
Exercise: 4 stars, standard, optional (elements)
Prove the correctness of the elements function. Because elements does not pay attention to colors, and does not rebalance the tree, then its proof should be a simple copy-paste from SearchTree, with only minor edits.
Fixpoint elements' (s: tree) (base: list (key*V)) : list (key * V) :=
match s with
| E ⇒ base
| T _ a k v b ⇒ elements' a ((k,v) :: elements' b base)
end.
Definition elements (s: tree) : list (key * V) := elements' s nil.
Definition elements_property (t: tree) (m: total_map V) : Prop :=
∀k v,
(In (k,v) (elements t) → m (int2Z k) = v) ∧
(m (int2Z k) ≠ default →
∃k', int2Z k = int2Z k' ∧ In (k', m (int2Z k)) (elements t)).
Theorem elements_relate:
∀t m,
SearchTree t →
Abs t m →
elements_property t m.
Proof.
(* FILL IN HERE *) Admitted.
☐
match s with
| E ⇒ base
| T _ a k v b ⇒ elements' a ((k,v) :: elements' b base)
end.
Definition elements (s: tree) : list (key * V) := elements' s nil.
Definition elements_property (t: tree) (m: total_map V) : Prop :=
∀k v,
(In (k,v) (elements t) → m (int2Z k) = v) ∧
(m (int2Z k) ≠ default →
∃k', int2Z k = int2Z k' ∧ In (k', m (int2Z k)) (elements t)).
Theorem elements_relate:
∀t m,
SearchTree t →
Abs t m →
elements_property t m.
Proof.
(* FILL IN HERE *) Admitted.
Verification of Balance
The Red-Black Invariants
Inductive is_redblack : tree → color → nat → Prop :=
| IsRB_leaf: ∀c, is_redblack E c 0
| IsRB_r: ∀l k v r n,
is_redblack l Red n →
is_redblack r Red n →
is_redblack (T Red l k v r) Black n
| IsRB_b: ∀c l k v r n,
is_redblack l Black n →
is_redblack r Black n →
is_redblack (T Black l k v r) c (S n).
| IsRB_leaf: ∀c, is_redblack E c 0
| IsRB_r: ∀l k v r n,
is_redblack l Red n →
is_redblack r Red n →
is_redblack (T Red l k v r) Black n
| IsRB_b: ∀c l k v r n,
is_redblack l Black n →
is_redblack r Black n →
is_redblack (T Black l k v r) c (S n).
To satisfy the red-black invariant, then, is to satisfy
is_redblack for some parent color and black height.
Definition rb (t : tree) : Prop :=
∃c n, is_redblack t c n.
∃c n, is_redblack t c n.
Theorem empty_rb : rb empty_tree.
Proof. (* FILL IN HERE *) Admitted.
☐
Proof. (* FILL IN HERE *) Admitted.
Insert and RB
Exercise: 1 star, standard (is_redblack_toblack)
Lemma is_redblack_toblack : ∀s n,
is_redblack s Red n → is_redblack s Black n.
Proof.
(* FILL IN HERE *) Admitted.
☐
is_redblack s Red n → is_redblack s Black n.
Proof.
(* FILL IN HERE *) Admitted.
Lemma is_redblack_makeblack : ∀s c n,
is_redblack s Black n
→ ∃n, is_redblack (makeBlack s) c n.
Proof.
(* FILL IN HERE *) Admitted.
☐
is_redblack s Black n
→ ∃n, is_redblack (makeBlack s) c n.
Proof.
(* FILL IN HERE *) Admitted.
Inductive nearly_redblack : tree → nat → Prop :=
| nrRB_r: ∀tl k kv tr n,
is_redblack tl Black n →
is_redblack tr Black n →
nearly_redblack (T Red tl k kv tr) n
| nrRB_b: ∀tl k kv tr n,
is_redblack tl Black n →
is_redblack tr Black n →
nearly_redblack (T Black tl k kv tr) (S n).
| nrRB_r: ∀tl k kv tr n,
is_redblack tl Black n →
is_redblack tr Black n →
nearly_redblack (T Red tl k kv tr) n
| nrRB_b: ∀tl k kv tr n,
is_redblack tl Black n →
is_redblack tr Black n →
nearly_redblack (T Black tl k kv tr) (S n).
Exercise: 4 stars, standard (ins_is_redblack)
Lemma ins_is_redblack:
∀x vx s n,
(is_redblack s Black n → nearly_redblack (ins x vx s) n) ∧
(is_redblack s Red n → is_redblack (ins x vx s) Black n).
Proof.
induction s; intro n; simpl; split;
intros; inv H; repeat constructor; auto.
*
destruct (IHs1 n); clear IHs1.
destruct (IHs2 n); clear IHs2.
specialize (H0 H6).
specialize (H2 H7).
clear H H1.
unfold balance.
(* FILL IN HERE *) Admitted.
☐
∀x vx s n,
(is_redblack s Black n → nearly_redblack (ins x vx s) n) ∧
(is_redblack s Red n → is_redblack (ins x vx s) Black n).
Proof.
induction s; intro n; simpl; split;
intros; inv H; repeat constructor; auto.
*
destruct (IHs1 n); clear IHs1.
destruct (IHs2 n); clear IHs2.
specialize (H0 H6).
specialize (H2 H7).
clear H H1.
unfold balance.
(* FILL IN HERE *) Admitted.
Corollary ins_red :
∀t k v n,
(is_redblack t Red n → is_redblack (ins k v t) Black n).
Proof.
intros. apply ins_is_redblack. assumption.
Qed.
Corollary ins_black :
∀t k v n,
(is_redblack t Black n → nearly_redblack (ins k v t) n).
Proof.
intros. apply ins_is_redblack. assumption.
Qed.
∀t k v n,
(is_redblack t Red n → is_redblack (ins k v t) Black n).
Proof.
intros. apply ins_is_redblack. assumption.
Qed.
Corollary ins_black :
∀t k v n,
(is_redblack t Black n → nearly_redblack (ins k v t) n).
Proof.
intros. apply ins_is_redblack. assumption.
Qed.
Exercise: 3 stars, standard (insert_is_redblack)
You will find ins_red, ins_black, and is_redblack_makeblack useful here.
Lemma insert_is_redblack: ∀k v t c n,
is_redblack t c n →
∃c' n',
is_redblack (insert k v t) c' n'.
Proof.
(* FILL IN HERE *) Admitted.
☐
is_redblack t c n →
∃c' n',
is_redblack (insert k v t) c' n'.
Proof.
(* FILL IN HERE *) Admitted.
Theorem insert_rb: ∀k v t,
rb t → rb (insert k v t).
Proof. (* FILL IN HERE *) Admitted.
☐
rb t → rb (insert k v t).
Proof. (* FILL IN HERE *) Admitted.
End TREES.
Extraction "redblack.ml" empty_tree insert lookup elements.
You can run this inside the OCaml top level by:
On my machine, in the byte-code interpreter this prints,
You can compile and run this with the OCaml native-code compiler by:
On my machine this prints,
#use "redblack.ml";; #use "test_searchtree.ml";;
Insert and lookup 1000000 random integers in 0.889 seconds. Insert and lookup 20000 random integers in 0.016 seconds. Insert and lookup 20000 consecutive integers in 0.015 seconds.
$ ocamlopt -c redblack.mli redblack.ml $ ocamlopt redblack.cmx -open Redblack test_searchtree.ml -o test_redblack $ ./test_redblack
Insert and lookup 1000000 random integers in 0.436 seconds. Insert and lookup 20000 random integers in 0. seconds. Insert and lookup 20000 consecutive integers in 0. seconds.
Success!
The benchmark measurements above (and in Extract) demonstrate that:- On random insertions, red-black trees are slightly faster than ordinary BSTs (red-black 0.436 seconds, vs ordinary 0.468 seconds)
- On consecutive insertions, red-black trees are much faster than ordinary BSTs (red-black 0. seconds, vs ordinary 0.374 seconds)