Theory StronglyJoinableRBT
section "Strongly Joinable Red-Black Trees"
theory StronglyJoinableRBT
imports
"StronglyJoinable"
begin
text ‹
The second instantiation of @{locale StronglyJoinable} covers red-black trees. The
‹join› of \<^cite>‹blelloch2022joinable› stores the black height in every node and
reads it in constant time, so the metadata field of the trees holds the colour together
with the black height. The library implementation in
‹HOL-Data_Structures.Set2_Join_RBT› recomputes the black height on every step
instead, which would charge ‹join› a cost linear in the ranks of its inputs rather than
in their difference. Its rebalancing steps are, however, taken over unchanged.
Two further deviations from the library concern the colour of the root. The library
‹join› paints the root black unconditionally and creates a black node whenever the two
inputs have equal black height. Following the paper, the root is painted black only when
it is red with a red child, and two black inputs of equal black height are combined by
a red node. Both are necessary for the rank of a join to exceed the larger input rank by
at most one.
›
subsection "Red-black trees with stored black height"
datatype color = Red | Black
type_synonym 'a rbt = "('a * (color * nat)) tree"
text ‹Both the colour and the black height are read from the node.›
fun col :: "'a rbt ⇒ color" where
"col Leaf = Black" |
"col (Node _ (_, (c, _)) _) = c"
fun bh :: "'a rbt ⇒ nat" where
"bh Leaf = 0" |
"bh (Node _ (_, (_, h)) _) = h"
fun R :: "'a rbt ⇒ 'a ⇒ 'a rbt ⇒ 'a rbt" where
"R l a r = Node l (a, (Red, bh l)) r"
fun B :: "'a rbt ⇒ 'a ⇒ 'a rbt ⇒ 'a rbt" where
"B l a r = Node l (a, (Black, bh l + 1)) r"
lemma neq_Black[simp]: "(c ≠ Black) = (c = Red)"
by (cases c) auto
lemma neq_Red[simp]: "(c ≠ Red) = (c = Black)"
by (cases c) auto
subsubsection "Invariants"
fun invc :: "'a rbt ⇒ bool" where
"invc Leaf = True" |
"invc (Node l (_, (c, _)) r) = ((c = Red ⟶ col l = Black ∧ col r = Black) ∧ invc l ∧ invc r)"
text ‹The weaker colour invariant allows a red root with a red child.›
fun invc2 :: "'a rbt ⇒ bool" where
"invc2 Leaf = True" |
"invc2 (Node l _ r) = (invc l ∧ invc r)"
fun invh :: "'a rbt ⇒ bool" where
"invh Leaf = True" |
"invh (Node l (_, (c, h)) r) =
(bh l = bh r ∧ h = (if c = Black then bh l + 1 else bh l) ∧ invh l ∧ invh r)"
abbreviation rbt :: "'a rbt ⇒ bool" where
"rbt t ≡ invc t ∧ invh t"
lemma invc2I: "invc t ⟹ invc2 t"
by (cases t rule: tree2_cases) auto
subsubsection "Rebalancing"
text ‹The rotations ‹baliL› and ‹baliR› of the library, restated for the stored
black heights.›
fun baliL :: "'a rbt ⇒ 'a ⇒ 'a rbt ⇒ 'a rbt" where
"baliL (Node (Node t1 (a, (Red, _)) t2) (b, (Red, _)) t3) c t4 = R (B t1 a t2) b (B t3 c t4)" |
"baliL (Node t1 (a, (Red, _)) (Node t2 (b, (Red, _)) t3)) c t4 = R (B t1 a t2) b (B t3 c t4)" |
"baliL t1 a t2 = B t1 a t2"
fun baliR :: "'a rbt ⇒ 'a ⇒ 'a rbt ⇒ 'a rbt" where
"baliR t1 a (Node t2 (b, (Red, _)) (Node t3 (c, (Red, _)) t4)) = R (B t1 a t2) b (B t3 c t4)" |
"baliR t1 a (Node (Node t2 (b, (Red, _)) t3) (c, (Red, _)) t4) = R (B t1 a t2) b (B t3 c t4)" |
"baliR t1 a t2 = B t1 a t2"
lemma inv_baliL:
"⟦ invh l; invh r; invc2 l; invc r; bh l = bh r ⟧
⟹ invc (baliL l a r) ∧ invh (baliL l a r) ∧ bh (baliL l a r) = bh l + 1"
by (induct l a r rule: baliL.induct) auto
lemma inv_baliR:
"⟦ invh l; invh r; invc l; invc2 r; bh l = bh r ⟧
⟹ invc (baliR l a r) ∧ invh (baliR l a r) ∧ bh (baliR l a r) = bh l + 1"
by (induct l a r rule: baliR.induct) auto
lemma set_baliL: "set_tree (baliL l a r) = set_tree l ∪ {a} ∪ set_tree r"
by (cases "(l,a,r)" rule: baliL.cases) auto
lemma set_baliR: "set_tree (baliR l a r) = set_tree l ∪ {a} ∪ set_tree r"
by (cases "(l,a,r)" rule: baliR.cases) auto
lemma bst_baliL:
"⟦bst l; bst r; ∀x∈set_tree l. x < a; ∀x∈set_tree r. a < x⟧ ⟹ bst (baliL l a r)"
by (cases "(l,a,r)" rule: baliL.cases) (auto simp: ball_Un)
lemma bst_baliR:
"⟦bst l; bst r; ∀x∈set_tree l. x < a; ∀x∈set_tree r. a < x⟧ ⟹ bst (baliR l a r)"
by (cases "(l,a,r)" rule: baliR.cases) (auto simp: ball_Un)
lemma size_baliL: "size (baliL l a r) = size l + size r + 1"
by (cases "(l,a,r)" rule: baliL.cases) auto
lemma size_baliR: "size (baliR l a r) = size l + size r + 1"
by (cases "(l,a,r)" rule: baliR.cases) auto
subsection "Join for Red-Black Trees"
fun joinL :: "'a rbt ⇒ 'a ⇒ 'a rbt ⇒ 'a rbt" where
"joinL l x r =
(if bh r ≤ bh l then R l x r
else case r of Node l' (x', (c, _)) r' ⇒
(if c = Black then baliL (joinL l x l') x' r' else R (joinL l x l') x' r'))"
fun joinR :: "'a rbt ⇒ 'a ⇒ 'a rbt ⇒ 'a rbt" where
"joinR l x r =
(if bh l ≤ bh r then R l x r
else case l of Node l' (x', (c, _)) r' ⇒
(if c = Black then baliR l' x' (joinR r' x r) else R l' x' (joinR r' x r)))"
declare joinL.simps[simp del]
declare joinR.simps[simp del]
fun blacken :: "'a rbt ⇒ 'a rbt" where
"blacken (Node l (a, (Red, h)) r) =
(if col l = Red ∨ col r = Red then B l a r else Node l (a, (Red, h)) r)" |
"blacken t = t"
fun join :: "'a rbt ⇒ 'a ⇒ 'a rbt ⇒ 'a rbt" where
"join l x r =
(if bh r < bh l then blacken (joinR l x r)
else if bh l < bh r then blacken (joinL l x r)
else if col l = Black ∧ col r = Black then R l x r else B l x r)"
subsection "Proof of Correctness"
subsubsection "Colour and height invariants"
lemma inv_joinL:
"⟦ invc l; invc r; invh l; invh r; bh l ≤ bh r ⟧ ⟹
invc2 (joinL l x r) ∧ (bh l ≠ bh r ∧ col r = Black ⟶ invc (joinL l x r))
∧ invh (joinL l x r) ∧ bh (joinL l x r) = bh r"
proof (induct l x r rule: joinL.induct)
case (1 l x r)
then show ?case
by (auto simp: inv_baliL invc2I joinL.simps[of l x r] split!: tree.splits if_splits)
qed
lemma inv_joinR:
"⟦ invc l; invc r; invh l; invh r; bh r ≤ bh l ⟧ ⟹
invc2 (joinR l x r) ∧ (bh l ≠ bh r ∧ col l = Black ⟶ invc (joinR l x r))
∧ invh (joinR l x r) ∧ bh (joinR l x r) = bh l"
proof (induct l x r rule: joinR.induct)
case (1 l x r)
then show ?case
by (auto simp: inv_baliR invc2I joinR.simps[of l x r] split!: tree.splits if_splits)
qed
text ‹A red root is passed upwards unchanged.›
lemma col_joinL: "⟦bh l < bh r; col r = Red⟧ ⟹ col (joinL l x r) = Red"
by (auto simp: joinL.simps[of l x r] split!: tree.splits if_splits)
lemma col_joinR: "⟦bh r < bh l; col l = Red⟧ ⟹ col (joinR l x r) = Red"
by (auto simp: joinR.simps[of l x r] split!: tree.splits if_splits)
lemma inv_blacken: "⟦invc2 t; invh t⟧ ⟹ invc (blacken t) ∧ invh (blacken t)"
by (cases t rule: blacken.cases) auto
lemma blacken_id: "invc t ⟹ blacken t = t"
by (cases t rule: blacken.cases) auto
lemma inv_join:
assumes "rbt l" "rbt r"
shows "rbt (join l x r)"
proof -
have "rbt (blacken (joinR l x r))" if "bh r < bh l"
using inv_joinR[of l r x] inv_blacken[of "joinR l x r"] assms that by auto
moreover have "rbt (blacken (joinL l x r))" if "bh l < bh r"
using inv_joinL[of l r x] inv_blacken[of "joinL l x r"] assms that by auto
ultimately show ?thesis
using assms by auto
qed
subsubsection "Set and search-tree properties"
lemma set_joinL: "set_tree (joinL l x r) = set_tree l ∪ {x} ∪ set_tree r"
proof (induction l x r rule: joinL.induct)
case (1 l x r)
then show ?case
by (auto simp: set_baliL joinL.simps[of l x r] split!: tree.splits if_splits)
qed
lemma set_joinR: "set_tree (joinR l x r) = set_tree l ∪ {x} ∪ set_tree r"
proof (induction l x r rule: joinR.induct)
case (1 l x r)
then show ?case
by (auto simp: set_baliR joinR.simps[of l x r] split!: tree.splits if_splits)
qed
lemma set_blacken: "set_tree (blacken t) = set_tree t"
by (cases t rule: blacken.cases) auto
lemma set_join: "set_tree (join l x r) = set_tree l ∪ {x} ∪ set_tree r"
by (auto simp: set_joinL set_joinR set_blacken)
lemma bst_joinL: "bst (Node l (a, n) r) ⟹ bst (joinL l a r)"
proof (induction l a r rule: joinL.induct)
case (1 l a r)
then show ?case
by (auto simp: set_baliL joinL.simps[of l a r] set_joinL ball_Un intro!: bst_baliL
split!: tree.splits if_splits)
qed
lemma bst_joinR: "bst (Node l (a, n) r) ⟹ bst (joinR l a r)"
proof (induction l a r rule: joinR.induct)
case (1 l a r)
then show ?case
by (auto simp: set_baliR joinR.simps[of l a r] set_joinR ball_Un intro!: bst_baliR
split!: tree.splits if_splits)
qed
lemma bst_blacken: "bst (blacken t) = bst t"
by (cases t rule: blacken.cases) auto
lemma bst_join: "bst (Node l (a, n) r) ⟹ bst (join l a r)"
by (auto simp: bst_blacken bst_joinL bst_joinR)
subsubsection "Size"
lemma size_joinL: "size (joinL l x r) = size l + size r + 1"
proof (induction l x r rule: joinL.induct)
case (1 l x r)
then show ?case
by (auto simp: size_baliL joinL.simps[of l x r] split!: tree.splits if_splits)
qed
lemma size_joinR: "size (joinR l x r) = size l + size r + 1"
proof (induction l x r rule: joinR.induct)
case (1 l x r)
then show ?case
by (auto simp: size_baliR joinR.simps[of l x r] split!: tree.splits if_splits)
qed
lemma size_blacken: "size (blacken t) = size t"
by (cases t rule: blacken.cases) auto
lemma join_size: "size (join l x r) = size l + size r + 1"
by (auto simp: size_joinL size_joinR size_blacken)
text ‹Functional correctness is established by instantiating @{locale Set2_Join}.›