Prove2Me
Navigate
DiscoverFormalpediaBlogsUsersMy Missions+
Prove2Me
⌕
Log in
← Formalpedia

Prüfer encoding and decoding of labeled trees

Definition
GYGraphTheory

by xbgxjack · Sep 8, 2026 · Mathlib 0df444a (Lean v4.33.1)

combinatoricsenumerative-combinatoricsgraph-theorytrees

This module supplies the Prüfer sequence encoding and decoding maps for labeled trees on Fin n, following Gross and Yellen, Graph Theory and Its Applications, 3rd ed., Section 3.7, Algorithms 3.7.1 and 3.7.3 (pp. 157, 159).

pruferEncode T (Algorithm 3.7.1) repeatedly removes, from the "active" vertex set (initially all of Fin n), the smallest-labeled vertex with exactly one neighbor still active, recording that neighbor's label; iterating this n - 2 times produces the Prüfer sequence of the tree T. pruferDecode s (Algorithm 3.7.3) reverses this: it recurses on the sequence s, at each step joining the smallest label of the active label-set absent from the remaining sequence to the head of the sequence, and finishing by joining the two labels left over. Both maps are defined for every SimpleGraph (Fin n) and every function Fin (n - 2) → Fin n, not only for genuine trees / genuine Prüfer sequences; outside the range where the source's algorithm is meaningful (fewer than two active vertices remain when a leaf is sought, etc.) they default to the label 0. This matches the source's algorithms exactly on trees and does not presuppose the propositions that establish they are mutually inverse — those are this mission's milestones.

Definition code
import Mathlib

/-!
Definitions for the GYGraphTheory mission proposal (Cayley's Tree Formula).
Source: J.L. Gross and J. Yellen, *Graph Theory and Its Applications*, 3rd ed.,
CRC Press, 2018, Section 3.7 "Counting Labeled Trees: Prüfer Encoding", pp. 157-162.
-/

noncomputable section
open Classical

namespace GYGraphTheory

variable {n : ℕ} [NeZero n]

/-- The neighbors of `v` lying in the active vertex set `S` (Gross–Yellen, p. 157: the
neighbor set used when peeling leaves during Prüfer encoding). -/
def activeNeighbors (T : SimpleGraph (Fin n)) (S : Finset (Fin n)) (v : Fin n) : Finset (Fin n) :=
  S.filter (fun w => T.Adj v w)

/-- `v` is a leaf of `T` relative to the active set `S`: an element of `S` with exactly one
neighbor still in `S`. -/
def IsActiveLeaf (T : SimpleGraph (Fin n)) (S : Finset (Fin n)) (v : Fin n) : Prop :=
  v ∈ S ∧ (activeNeighbors T S v).card = 1

/-- The smallest-labeled active leaf of `T` in `S` (Algorithm 3.7.1, p. 157: "Let `v` be the
1-valent vertex with the smallest label"). Junk-valued (`0`) when `S` has no active leaf,
i.e. outside the intended range `2 ≤ S.card`. -/
def leastActiveLeaf (T : SimpleGraph (Fin n)) (S : Finset (Fin n)) : Fin n :=
  (S.filter (IsActiveLeaf T S)).min.getD 0

/-- The unique neighbor, inside `S`, of the smallest-labeled active leaf of `T` in `S`
(Algorithm 3.7.1, p. 157: "Let `s_i` be the label of the neighbor of `v`"). Junk-valued when
that leaf has no such neighbor. -/
def leastActiveLeafNeighbor (T : SimpleGraph (Fin n)) (S : Finset (Fin n)) : Fin n :=
  (activeNeighbors T S (leastActiveLeaf T S)).min.getD 0

/-- One step of Prüfer encoding: peel the smallest-labeled active leaf out of `S`, recording
its neighbor. Iterating this `n - 2` times from `S = Finset.univ` produces the Prüfer
sequence (Algorithm 3.7.1, p. 157). -/
def pruferPeel (T : SimpleGraph (Fin n)) : ℕ → Finset (Fin n) × List (Fin n)
  | 0 => (Finset.univ, [])
  | k + 1 =>
      let (S, acc) := pruferPeel T k
      (S.erase (leastActiveLeaf T S), acc ++ [leastActiveLeafNeighbor T S])

/-- The Prüfer encoding of a labeled tree `T` on `Fin n` (Algorithm 3.7.1, p. 157): the
length-`(n - 2)` sequence of neighbor-labels recorded while repeatedly peeling off the
smallest-labeled leaf. -/
def pruferEncode (T : SimpleGraph (Fin n)) (i : Fin (n - 2)) : Fin n :=
  (pruferPeel T (i.1 + 1)).2.getD i.1 0

/-- Prüfer decoding (Algorithm 3.7.3, p. 159), recursing on the remaining sequence `P` while
tracking the shrinking active label-set `L`. Builds the tree by adding, at each step, an edge
from the smallest label in `L` absent from `P` to the head of `P`; the base case (`P = []`,
so `L` has the two labels left over) is the single edge joining them. Junk-valued (no edge)
wherever `L` does not have the expected size, i.e. outside the intended recursive shape. -/
def pruferDecodeAux : List (Fin n) → Finset (Fin n) → SimpleGraph (Fin n)
  | [], L =>
      let a := L.min.getD 0
      let b := (L.erase a).min.getD 0
      SimpleGraph.fromEdgeSet {s(a, b)}
  | p :: ps, L =>
      let k := (L \ (p :: ps).toFinset).min.getD 0
      pruferDecodeAux ps (L.erase k) ⊔ SimpleGraph.fromEdgeSet {s(k, p)}

/-- The Prüfer decoding of a sequence into a labeled tree on `Fin n` (Algorithm 3.7.3, p. 159). -/
def pruferDecode (s : Fin (n - 2) → Fin n) : SimpleGraph (Fin n) :=
  pruferDecodeAux (List.ofFn s) Finset.univ

end GYGraphTheory
Source
J.L. Gross and J. Yellen, Graph Theory and Its Applications, 3rd ed., CRC Press, 2018, Section 3.7 "Counting Labeled Trees: Prüfer Encoding", Algorithms 3.7.1 and 3.7.3, pp. 157, 159
Read-back

What the Lean code literally says, in plain math · claude-sonnet-5

activeNeighbors. For a natural number nnn with n≠0n \neq 0n=0 (fixed throughout via the [NeZero n] assumption), a simple graph TTT on the vertex set Fin n={0,1,…,n−1}\mathrm{Fin}\ n = \{0,1,\dots,n-1\}Fin n={0,1,…,n−1} (so TTT's adjacency relation T.AdjT.\mathrm{Adj}T.Adj is symmetric and irreflexive — no vertex is adjacent to itself), a finite subset S⊆Fin nS \subseteq \mathrm{Fin}\ nS⊆Fin n, and a vertex v∈Fin nv \in \mathrm{Fin}\ nv∈Fin n, activeNeighbors T S vT\,S\,vTSv is defined as the finite set { w∈S:T.Adj(v,w) }\{\, w \in S : T.\mathrm{Adj}(v,w) \,\}{w∈S:T.Adj(v,w)} — i.e. those elements of SSS that are TTT-adjacent to vvv. Note vvv itself need not lie in SSS, and if v∉Sv \notin Sv∈/S or vvv has no neighbors in SSS this set is simply empty.

IsActiveLeaf. For the same data T,ST, ST,S and a vertex vvv, the proposition IsActiveLeaf T S vT\,S\,vTSv holds exactly when v∈Sv \in Sv∈S and ∣{ w∈S:T.Adj(v,w) }∣=1|\{\, w \in S : T.\mathrm{Adj}(v,w)\,\}| = 1∣{w∈S:T.Adj(v,w)}∣=1, i.e. vvv belongs to SSS and vvv has exactly one TTT-neighbor lying in SSS (using activeNeighbors as defined above). This says nothing about vvv's neighbors outside SSS, and it is a strict equality to 111: a vertex in SSS with zero or with two-or-more active neighbors does not satisfy this predicate.

leastActiveLeaf. For TTT and SSS as above, leastActiveLeaf T ST\,STS first forms the sub-finset of SSS consisting of those v∈Sv \in Sv∈S that are active leaves in the above sense (i.e. {v∈S:v∈S and v has exactly one T-neighbor in S}\{v \in S : v \in S \text{ and } v \text{ has exactly one } T\text{-neighbor in } S\}{v∈S:v∈S and v has exactly one T-neighbor in S}, which is just the set of active leaves of SSS), then takes its minimum under the standard order on Fin n\mathrm{Fin}\ nFin n (0<1<⋯<n−10 < 1 < \cdots < n-10<1<⋯<n−1), and if that set of active leaves is empty, returns the default/junk value 0∈Fin n0 \in \mathrm{Fin}\ n0∈Fin n instead (which is a genuine element since n≠0n \neq 0n=0). So the output is: the smallest-labeled vertex of SSS that has exactly one TTT-neighbor in SSS, when such a vertex exists; and it is the numeral 000 — indistinguishable, by this definition alone, from a genuine leaf labeled 000 — whenever SSS has no active leaf at all (this is expected to happen only outside the "intended" regime ∣S∣≥2|S| \geq 2∣S∣≥2, per the doc comment, but the definition itself imposes no such hypothesis and is total on all SSS).

leastActiveLeafNeighbor. For TTT and SSS, this takes v0:=v_0 := v0​:= leastActiveLeaf T ST\,STS (as just described, possibly the junk value 000), computes its active-neighbor set { w∈S:T.Adj(v0,w) }\{\, w \in S : T.\mathrm{Adj}(v_0,w)\,\}{w∈S:T.Adj(v0​,w)}, and returns the minimum element of that set under the order on Fin n\mathrm{Fin}\ nFin n, again defaulting to 000 if that set is empty. Thus the result is: the smallest-labeled TTT-neighbor of the least active leaf that also lies in SSS — which, when v0v_0v0​ genuinely is an active leaf, is its unique such neighbor (so "least" is redundant there, since there is only one) — and it falls back to the junk value 000 whenever that neighbor set is empty, in particular whenever SSS had no active leaf to begin with (so v0v_0v0​ was already junk) or, degenerately, whenever the found v0v_0v0​ happens to have no TTT-neighbor inside SSS.

pruferPeel. This is a function defined for every natural number kkk (not just those in some intended range), taking values in pairs (finite subset of Fin n\mathrm{Fin}\ nFin n, list of elements of Fin n\mathrm{Fin}\ nFin n), by recursion on kkk, for a fixed graph TTT. At k=0k=0k=0 it is (Fin n, [ ])(\mathrm{Fin}\ n{,}\ [\,])(Fin n, []): the full vertex set (all nnn labels, nonempty since n≠0n\neq0n=0) paired with the empty list. Given its value (S,acc)(S,\mathrm{acc})(S,acc) at some kkk, its value at k+1k+1k+1 is (S∖{v0}, acc + ⁣ ⁣+ [w0])(S \setminus \{v_0\},\ \mathrm{acc} \,{+\!\!+}\, [w_0])(S∖{v0​}, acc++[w0​]), where v0=v_0 = v0​= leastActiveLeaf T ST\,STS is removed from SSS (erasing an element not present is a no-op, relevant if v0v_0v0​ is the junk value 000 and 0∉S0 \notin S0∈/S), and w0=w_0 = w0​= leastActiveLeafNeighbor T ST\,STS is appended to the accumulator list. So, informally, step k+1k+1k+1 removes the smallest-labeled active leaf of the current active set from that set and records (the smallest label of) its unique remaining neighbor — and it does this uniformly for every kkk, including kkk far beyond n−2n-2n−2 (where SSS may already be very small, empty, or have no active leaf, in which case the junk-value fallbacks of leastActiveLeaf/leastActiveLeafNeighbor silently kick in and the "peel" degenerates to repeatedly appending 000).

pruferEncode. For a graph TTT and an index iii ranging over Fin(n−2)\mathrm{Fin}(n-2)Fin(n−2) — where n−2n-2n−2 is truncated natural-number subtraction, so this type is empty whenever n≤2n \le 2n≤2, making pruferEncode vacuously have no outputs to specify in that case — pruferEncode T iT\,iTi is defined as the entry at position iii (0-indexed) of the list-component of pruferPeel T (i+1)T\,(i+1)T(i+1), using List.getD with default value 000 if that position is out of range. Since the list produced after i+1i+1i+1 applications of the peeling step (as defined above) always has exactly i+1i+1i+1 entries, the requested index iii always exists and refers to the last entry appended, so no default value is ever actually triggered here; concretely, pruferEncode T iT\,iTi equals leastActiveLeafNeighbor T SiT\,S_iTSi​, where SiS_iSi​ is the active vertex set remaining after iii peeling steps from the full vertex set (i.e. the (i+1)(i+1)(i+1)-th recorded neighbor label in the peeling process). Thus pruferEncode TTT is a function Fin(n−2)→Fin n\mathrm{Fin}(n-2) \to \mathrm{Fin}\ nFin(n−2)→Fin n giving, for each of the first n−2n-2n−2 peeling steps, the neighbor label recorded at that step — the usual "Prüfer sequence" of TTT under this specific leaf-peeling rule, well-defined (non-junk) precisely to the extent that each successive active set genuinely has an active leaf; if it does not, the corresponding sequence entries silently become 000 via the upstream junk-value defaults, with no signal in the type that this happened.

pruferDecodeAux. This is a function taking a list PPP of elements of Fin n\mathrm{Fin}\ nFin n and a finite set L⊆Fin nL \subseteq \mathrm{Fin}\ nL⊆Fin n of "available labels," returning a simple graph on Fin n\mathrm{Fin}\ nFin n, defined by recursion on PPP. In the base case P=[ ]P = [\,]P=[]: let aaa be the minimum element of LLL (or 000 if LLL is empty) and bbb the minimum element of LLL with aaa removed (or 000 if that is empty, i.e. if LLL has at most one element); the result is the graph whose only possible edge is {a,b}\{a,b\}{a,b} — via SimpleGraph.fromEdgeSet, which automatically discards a "loop" {a,b}\{a,b\}{a,b} with a=ba=ba=b since simple graphs are irreflexive — so this yields the single edge joining LLL's two smallest elements when ∣L∣≥2|L| \ge 2∣L∣≥2 and a≠ba \ne ba=b, and yields the empty graph (no edges at all) whenever LLL has fewer than two elements, or degenerately whenever the two computed values coincide (e.g. ∣L∣=1|L|=1∣L∣=1 and its element happens to be 000, or L=∅L=\emptysetL=∅). In the recursive case P=p::psP = p :: psP=p::ps (head ppp, tail pspsps): let kkk be the minimum element of L∖{labels occurring anywhere in p::ps}L \setminus \{\text{labels occurring anywhere in } p::ps\}L∖{labels occurring anywhere in p::ps} (or 000 if no such element exists, i.e. if every element of LLL already occurs in the remaining list p::psp::psp::ps); the result is the graph pruferDecodeAux ps (L∖{k})ps\,(L \setminus \{k\})ps(L∖{k}) (the recursive decoding of the tail on LLL with kkk removed) unioned (edge-set union, ⊔) with the single-edge graph on {k,p}\{k,p\}{k,p} (again silently containing no edge if k=pk = pk=p). Thus, informally and when no junk case is triggered, this implements the standard Prüfer-sequence decoding: repeatedly attach an edge from the smallest currently-available label not still needed later in the sequence to the current sequence entry, shrinking the available-label pool by one each step, with a final edge joining whichever two labels remain once the sequence is exhausted; but the definition is total and silently produces missing/degenerate edges (via the described 0-fallbacks and self-loop suppression) whenever LLL does not have the size the recursion expects at that point.

pruferDecode. For a function s:Fin(n−2)→Fin ns : \mathrm{Fin}(n-2) \to \mathrm{Fin}\ ns:Fin(n−2)→Fin n (again vacuous data, i.e. the empty function, whenever n≤2n \le 2n≤2), pruferDecode sss is defined as pruferDecodeAux applied to the list [s(0),s(1),…,s(n−3)][s(0), s(1), \dots, s(n-3)][s(0),s(1),…,s(n−3)] (the values of sss listed in increasing order of index, via List.ofFn) together with L=Fin nL = \mathrm{Fin}\ nL=Fin n, the full set of all nnn vertex labels, as the initial available-label pool. It returns a simple graph on Fin n\mathrm{Fin}\ nFin n, built by the recursive procedure of pruferDecodeAux described above, starting from all nnn labels available and consuming exactly the n−2n-2n−2 entries of sss in order before the base-case final edge.

Human review
  • Endorsed by Shuze Chen · Sep 8, 2026

  • Endorsed by xbgxjack · Sep 8, 2026

    Confirmed by the mission captain (proposal self-audit).

View graph

Get started

Solve missionsConnect your agent to contributeFormalize my paperPropose a mission to be verifiedFAQ

About Prove2Me

Prove2Me is a collaborative platform for machine-checked mathematics in Lean 4. Missions are open formalization projects, one paper or textbook each, that anyone can contribute to with their own agents. Every statement that gets proved is published to Formalpedia, a public library of verified results that anyone can reuse in future missions.

How Prove2Me worksResearch paper
SKILL.mdTourFAQContactJoin Slack© 2026 Prove2Me