Imports
import Mathlib

CLRS Section 19.1 - Disjoint-set operations

This section gives the representation-independent semantics for disjoint-set data structures. A state is a partition, presented by its equivalence relation. Merging two sets replaces the two old equivalence classes by their union and leaves every other class unchanged.

Main results:

  • Theorem Partition.merge_sameSet_iff: exact relational semantics of union.

  • Theorem stepSpec_union_sameSet_iff: the abstract UNION operation implements that merge.

  • Theorem runSpec_append: operation traces compose.

  • Theorem runSpec_preserves_sameSet: disjoint-set traces only merge classes; they never split an existing class.

namespace CLRSnamespace Chapter21

A representation-independent disjoint-set state.

structure Partition (α : Type*) where sameSet : α α Prop refl : x, sameSet x x symm : {x y}, sameSet x y sameSet y x trans : {x y z}, sameSet x y sameSet y z sameSet x z
namespace Partitionvariable {α : Type*}

The initial partition in which every element is a singleton.

def discrete : Partition α where sameSet := (· = ·) refl := Eq.refl symm := Eq.symm trans := Eq.trans
@[simp] theorem discrete_sameSet_iff {x y : α} : (discrete : Partition α).sameSet x y x = y := Iff.rfl

An element belongs to one of the two classes selected for a merge.

def touches (P : Partition α) (x y a : α) : Prop := P.sameSet a x P.sameSet a y
private theorem touches_of_sameSet_left (P : Partition α) {x y a b : α} (hab : P.sameSet a b) (hb : P.touches x y b) : P.touches x y a := by rcases hb with hbx | hby · exact Or.inl (P.trans hab hbx) · exact Or.inr (P.trans hab hby)

Merge the equivalence classes containing x and y.

def merge (P : Partition α) (x y : α) : Partition α where sameSet a b := P.sameSet a b (P.touches x y a P.touches x y b) refl a := Or.inl (P.refl a) symm := by intro a b hab rcases hab with hab | ha, hb · exact Or.inl (P.symm hab) · exact Or.inr hb, ha trans := by intro a b c hab hbc rcases hab with hab | ha, hb · rcases hbc with hbc | hb', hc · exact Or.inl (P.trans hab hbc) · exact Or.inr P.touches_of_sameSet_left hab hb', hc · rcases hbc with hbc | _, hc · exact Or.inr ha, P.touches_of_sameSet_left (P.symm hbc) hb · exact Or.inr ha, hc

The CLRS union formula: exactly the two selected classes become one.

theorem merge_sameSet_iff (P : Partition α) (x y a b : α) : (P.merge x y).sameSet a b P.sameSet a b (P.sameSet a x P.sameSet y b) (P.sameSet a y P.sameSet x b) := by constructor · intro h rcases h with hab | ha, hb · exact Or.inl hab · rcases ha with hax | hay <;> rcases hb with hbx | hby · exact Or.inl (P.trans hax (P.symm hbx)) · exact Or.inr (Or.inl hax, P.symm hby) · exact Or.inr (Or.inr hay, P.symm hbx) · exact Or.inl (P.trans hay (P.symm hby)) · intro h rcases h with hab | hax, hyb | hay, hxb · exact Or.inl hab · exact Or.inr Or.inl hax, Or.inr (P.symm hyb) · exact Or.inr Or.inr hay, Or.inl (P.symm hxb)

Merging a class with itself leaves the represented partition unchanged.

theorem merge_self_sameSet_iff (P : Partition α) (x a b : α) : (P.merge x x).sameSet a b P.sameSet a b := by rw [merge_sameSet_iff] constructor · intro h rcases h with hab | hax, hxb | hax, hxb · exact hab · exact P.trans hax hxb · exact P.trans hax hxb · exact Or.inl

Merging two elements already in one class leaves the partition unchanged.

theorem merge_related_sameSet_iff (P : Partition α) {x y a b : α} (hxy : P.sameSet x y) : (P.merge x y).sameSet a b P.sameSet a b := by rw [merge_sameSet_iff] constructor · intro h rcases h with hab | hax, hyb | hay, hxb · exact hab · exact P.trans (P.trans hax hxy) hyb · exact P.trans (P.trans hay (P.symm hxy)) hxb · exact Or.inl

A merge preserves every equivalence that already held.

theorem sameSet_merge_of_sameSet (P : Partition α) {x y a b : α} (h : P.sameSet a b) : (P.merge x y).sameSet a b := Or.inl h
end Partition

The two observable CLRS disjoint-set operations.

inductive Operation (α : Type*) where | find (x : α) | union (x y : α) deriving Repr

Abstract state transition for one disjoint-set operation.

def stepSpec {α : Type*} (P : Partition α) : Operation α Partition α | .find _ => P | .union x y => P.merge x y

Execute an abstract sequence of disjoint-set operations.

def runSpec {α : Type*} : Partition α List (Operation α) Partition α | P, [] => P | P, op :: ops => runSpec (stepSpec P op) ops
@[simp] theorem stepSpec_find {α : Type*} (P : Partition α) (x : α) : stepSpec P (.find x) = P := rfltheorem stepSpec_union_sameSet_iff {α : Type*} (P : Partition α) (x y a b : α) : (stepSpec P (.union x y)).sameSet a b P.sameSet a b (P.sameSet a x P.sameSet y b) (P.sameSet a y P.sameSet x b) := P.merge_sameSet_iff x y a b@[simp] theorem runSpec_nil {α : Type*} (P : Partition α) : runSpec P [] = P := rfl@[simp] theorem runSpec_cons {α : Type*} (P : Partition α) (op : Operation α) (ops : List (Operation α)) : runSpec P (op :: ops) = runSpec (stepSpec P op) ops := rfl

Running concatenated traces is the same as running them successively.

theorem runSpec_append {α : Type*} (P : Partition α) (xs ys : List (Operation α)) : runSpec P (xs ++ ys) = runSpec (runSpec P xs) ys := by induction xs generalizing P with | nil => rfl | cons op xs ih => simp only [List.cons_append, runSpec_cons] exact ih (stepSpec P op)

An operation trace may merge classes, but it never splits one.

theorem runSpec_preserves_sameSet {α : Type*} (P : Partition α) (ops : List (Operation α)) {a b : α} (h : P.sameSet a b) : (runSpec P ops).sameSet a b := by induction ops generalizing P with | nil => exact h | cons op ops ih => cases op with | find x => exact ih P h | union x y => exact ih (P.merge x y) (Or.inl h)
end Chapter21end CLRS