Nivel 8 · Quicksort verificado (experto)

Corrección I: permutación

La primera mitad de la

Definición 12.1

afirma que la salida de quicksort contiene exactamente los mismos elementos que la entrada, con las mismas multiplicidades. Primero se registra el único hecho combinatorio sobre el filtrado que la prueba necesita: partir una lista por una prueba booleana y concatenar las dos mitades es una permutación de la original.

Lema 12.7 (La partición por filtro es una permutación)

Para toda prueba booleana qq y lista \ell,

filter(q)() + ⁣ ⁣+ filter(λx.¬qx)()    .\mathsf{filter}(q)(\ell)\ +\!\!+\ \mathsf{filter}(\lambda x.\,\neg q\,x)(\ell)\ \ \sim\ \ \ell.
Demostración.

A probar: filter(q)()+ ⁣ ⁣+filter(qc)()\mathsf{filter}(q)(\ell) +\!\!+\, \mathsf{filter}(q^{c})(\ell)\sim\ell, donde se abrevia qc:=λx.¬qxq^{c}:=\lambda x.\,\neg q\,x. Por inducción sobre \ell.

  1. =[]\ell=[\,]. Ambos filtrados son [][\,], luego []+ ⁣ ⁣+[]=[][][\,] +\!\!+\, [\,]=[\,]\sim[\,] por reflexividad.
  2. =a::\ell=a::\ell'. Se distingue según qaq\,a.
    • Si qaq\,a se cumple: filter(q)(a::)=a::filter(q)()\mathsf{filter}(q)(a::\ell')=a::\mathsf{filter}(q)(\ell') y filter(qc)(a::)=filter(qc)()\mathsf{filter}(q^{c})(a::\ell')=\mathsf{filter}(q^{c})(\ell'), de modo que filter(q)(a::)+ ⁣ ⁣+filter(qc)(a::)=a::(filter(q)()+ ⁣ ⁣+filter(qc)()) HI a::.\mathsf{filter}(q)(a::\ell') +\!\!+\, \mathsf{filter}(q^{c})(a::\ell') = a::\big(\mathsf{filter}(q)(\ell') +\!\!+\, \mathsf{filter}(q^{c})(\ell')\big) \ \overset{\text{HI}}{\sim}\ a::\ell'.
    • Si qaq\,a falla: filter(q)(a::)=filter(q)()\mathsf{filter}(q)(a::\ell')=\mathsf{filter}(q)(\ell') y filter(qc)(a::)=a::filter(qc)()\mathsf{filter}(q^{c})(a::\ell')=a::\mathsf{filter}(q^{c})(\ell'), de modo que la concatenación es filter(q)()+ ⁣ ⁣+(a::filter(qc)())\mathsf{filter}(q)(\ell') +\!\!+\, \big(a::\mathsf{filter}(q^{c})(\ell')\big). Trasladar aa al frente es una permutación (las transposiciones adyacentes generan \sim), y se aplica la hipótesis de inducción: filter(q)()+ ⁣ ⁣+(a::filter(qc)())  a::(filter(q)()+ ⁣ ⁣+filter(qc)()) HI a::.\mathsf{filter}(q)(\ell') +\!\!+\, \big(a::\mathsf{filter}(q^{c})(\ell')\big) \ \sim\ a::\big(\mathsf{filter}(q)(\ell') +\!\!+\, \mathsf{filter}(q^{c})(\ell')\big) \ \overset{\text{HI}}{\sim}\ a::\ell'.

El teorema de permutación

Teorema 12.8 (Permutación)

Para toda lista \ell,  qsort()\ \mathsf{qsort}(\ell)\sim\ell.

Demostración.

A probar: qsort()\mathsf{qsort}(\ell)\sim\ell. Por inducción bien fundamentada sobre |\ell| (legítima por la Proposición 12.6).

  1. =[]\ell=[\,]. qsort([])=[][]\mathsf{qsort}([\,])=[\,]\sim[\,] por reflexividad.
  2. =p::r\ell=p::r. Las dos llamadas recursivas son sobre \ell_{\leq} y >\ell_{>}, ambas más cortas que p::rp::r (Proposición 12.6); la hipótesis de inducción da entonces qsort()\mathsf{qsort}(\ell_{\leq})\sim\ell_{\leq} y qsort(>)>\mathsf{qsort}(\ell_{>})\sim\ell_{>}. Se encadenan tres permutaciones por transitividad: qsort(p::r)=qsort()+ ⁣ ⁣+(p::qsort(>))  + ⁣ ⁣+(p::>)  p::(+ ⁣ ⁣+>)  p::r.\begin{aligned} \mathsf{qsort}(p::r)=\mathsf{qsort}(\ell_{\leq}) +\!\!+\, \big(p::\mathsf{qsort}(\ell_{>})\big) &\ \sim\ \ell_{\leq} +\!\!+\, \big(p::\ell_{>}\big) \\ &\ \sim\ p::(\ell_{\leq} +\!\!+\, \ell_{>}) \\ &\ \sim\ p::r. \end{aligned} La primera permutación es la congruencia de \sim para + ⁣ ⁣++\!\!+ y :::: aplicada a las dos hipótesis de inducción; la segunda es la permutación «del medio», que lleva el pivote pp al frente; y la tercera aplica el Lema 12.7 bajo la cabeza pp, pues + ⁣ ⁣+>r\ell_{\leq} +\!\!+\, \ell_{>}\sim r. Por transitividad, qsort(p::r)p::r\mathsf{qsort}(p::r)\sim p::r.
Corolario 12.9 (Se preserva la pertenencia)

Para todo aa y \ell,  aqsort()a\ a\in\mathsf{qsort}(\ell)\leftrightarrow a\in\ell.

Demostración.

A probar: aqsort()aa\in\mathsf{qsort}(\ell)\leftrightarrow a\in\ell. Por el Teorema 12.8, qsort()\mathsf{qsort}(\ell)\sim\ell; y dos listas permutadas contienen exactamente los mismos elementos (con las mismas multiplicidades), de donde, en particular, aqsort()aa\in\mathsf{qsort}(\ell)\leftrightarrow a\in\ell.

Este corolario, en apariencia modesto, es el puente hacia la mitad de ordenamiento: permitirá transportar la pertenencia a las salidas recursivas de vuelta a las mitades de la partición, donde la prueba del filtro da las cotas contra el pivote.

Lean

La prueba en Thesis/Sort/Quicksort.lean sigue la matemática línea por línea. Los dos have son las hipótesis de inducción (llamadas recursivas sobre las mitades más cortas); ih1.append (ih2.cons p) es el paso de congruencia; perm_middle lleva el pivote al frente; y filter_append_perm es el Lema 12.7.

theorem quicksort_perm : ∀ l : List α, quicksort l ~ l
| [] => by simp
| p :: rest => by
rw [quicksort_cons]
-- las llamadas recursivas permutan las dos mitades de la partición
have ih1 : quicksort (rest.filter (fun x => decide (x ≤ p)))
~ rest.filter (fun x => decide (x ≤ p)) := quicksort_perm _
have ih2 : quicksort (rest.filter (fun x => ! decide (x ≤ p)))
~ rest.filter (fun x => ! decide (x ≤ p)) := quicksort_perm _
-- pegar las mitades en torno al pivote y recombinar los dos filtros
refine (ih1.append (ih2.cons p)).trans ?_
refine (perm_middle).trans ?_
exact (filter_append_perm (fun x => decide (x ≤ p)) rest).cons p
termination_by l => l.length
decreasing_by
all_goals simp_wf
all_goals exact le_trans (length_filter_le _ _) (le_of_eq (by simp))
theorem mem_quicksort {a : α} {l : List α} : a ∈ quicksort l ↔ a ∈ l :=
(quicksort_perm l).mem_iff
Nota (La inducción sigue a la recursión)

Obsérvese que quicksort_perm lleva sus propias cláusulas termination_by y decreasing_by, idénticas a las de la definición: el teorema se prueba por la misma recursión bien fundamentada con la que se definió la función, y las llamadas quicksort_perm _ sobre las mitades son exactamente las hipótesis de inducción del argumento matemático. En Lean, «inducción bien fundamentada siguiendo la recursión» no es una metáfora: es literalmente el mismo mecanismo.