問題 55

(中級 🌟🌟) 完全平衡二分木を構成せよ。

完全平衡二分木では、すべてのノードについて「左部分木と右部分木のノード数の差が1以下」という性質が成り立つ。

与えられたノード数に対して、すべての完全平衡二分木を構成する関数 cbalTree を実装せよ。すべての解をバックトラックで生成すること。

注意

完全平衡二分木は(高さ)平衡二分木とは異なる。

二分木とその構文定義

/-- 2分木 -/
inductive BinTree (α : Type) where
  | empty
  | node (val : α) (left right : BinTree α)
deriving DecidableEq, BEq

/-- 空の二分木 -/
notation:max "∅" => BinTree.empty

variable {α : Type}

/-- 2分木の葉 -/
def BinTree.leaf (a : α) : BinTree α := .node a ∅ ∅

@[inherit_doc]
notation:max "⟦" x "⟧" => BinTree.leaf x

2分木の表示

protected def BinTree.toString [ToString α] (t : BinTree α) : String :=
  match t with
  | empty => "∅"
  | node val .empty .empty =>
    toString val
  | node val left right =>
    toString val ++ " * " ++ "(" ++ BinTree.toString left ++ " + " ++ BinTree.toString right ++ ")"

instance [ToString α] : ToString (BinTree α) where
  toString := BinTree.toString

#guard toString ⟦1⟧ = "1"
#guard toString (BinTree.node 1 ⟦2⟧ .empty) = "1 * (2 + ∅)"
#guard toString (BinTree.node 1 ⟦2⟧ ⟦3⟧) = "1 * (2 + 3)"
#guard
  let tree := BinTree.node 1
    (left := BinTree.node 2 ⟦3⟧ ⟦4⟧)
    (right := BinTree.node 5 ⟦6⟧ ⟦7⟧)
  toString tree = "1 * (2 * (3 + 4) + 5 * (6 + 7))"

2分木を表現する構文

open Lean

/-- 2分木を構成する構文カテゴリ -/
declare_syntax_cat bintree
syntax "[tree| " bintree "]" : term

/-- 木のラベル。ラベルとしては、数値リテラル・文字列リテラル・文字リテラルを許可する -/
syntax tree_label := num <|> str <|> char

/-- 基底ケース: `[tree| 42]`などは正しい構文 -/
syntax tree_label : bintree

/-- 基底ケース: `[tree| ∅]` は空の木に相当するので正しい構文 -/
syntax "∅" : bintree

/-- 再帰ステップ -/
syntax tree_label " * " "(" bintree " + " bintree ")" : bintree

/-- `tree_label`に属する構文を`term`に変換する -/
def expandTreeLabel (stx : TSyntax `tree_label) : MacroM (TSyntax `term) :=
  match stx with
  | `(tree_label| $num:num) => `(term| $num)
  | `(tree_label| $str:str) => `(term| $str)
  | `(tree_label| $char:char) => `(term| $char)
  | _ => Macro.throwUnsupported

macro_rules
  | `([tree| ∅]) => `(BinTree.empty)
  | `([tree| $v:tree_label]) => do
    let label ← expandTreeLabel v
    `(BinTree.leaf $label)
  | `([tree| $v:tree_label * ($l + $r)]) => do
    let label ← expandTreeLabel v
    `(BinTree.node $label [tree| $l] [tree| $r])

#guard
  let actual := [tree| 1 * (2 + 3)]
  let expected := BinTree.node 1 ⟦2⟧ ⟦3⟧
  actual = expected

#guard
  let actual := [tree| 12 * (2 + ∅)]
  let expected := BinTree.node 12 ⟦2⟧ ∅
  actual = expected

#guard
  let actual := [tree| 1 * (2 * (3 + 4) + 5 * (6 + 7))]
  let expected := BinTree.node 1
    (left := BinTree.node 2 ⟦3⟧ ⟦4⟧)
    (right := BinTree.node 5 ⟦6⟧ ⟦7⟧)
  actual = expected

Repr の定義

def BinTree.reprPrec [Repr α] (tree : BinTree α) : String :=
  "[tree| " ++ helper tree ++ "]"
where
  helper : BinTree α → String
  | .empty => "∅"
  | .node v .empty .empty => reprStr v
  | .node v left right =>
    reprStr v ++ " * " ++ "(" ++ helper left ++ " + " ++ helper right ++ ")"

instance [Repr α] : Repr (BinTree α) where
  reprPrec := fun tree _ => BinTree.reprPrec tree

#eval [tree| 1 * (2 + 3)]
#eval [tree| 12 * (2 + ∅)]
#eval [tree| 1 * (2 * (3 + 4) + 5 * (6 + 7))]
#eval [tree| 'x' * ('y' + 'z')]

回答

/-- 2分木のノード数 -/
def BinTree.nodes {α : Type} : BinTree α → Nat
  | .empty => 0
  | .node _ l r => 1 + l.nodes + r.nodes

/-- 2分木が完全平衡か判定 -/
def BinTree.isCBalanced {α : Type} : BinTree α → Bool
  | .empty => true
  | .node _ l r =>
    Int.natAbs ((l.nodes : Int) - (r.nodes : Int)) ≤ 1 && l.isCBalanced && r.isCBalanced

/-- List型のモナドインスタンス -/
instance : Monad List where
  pure x := [x]
  bind l f := l.flatMap f
  map f l := l.map f

/-- ノード数が `x` の完全平衡二分木をすべて構成する -/
partial def cbalTree (x : Nat) : List (BinTree Unit) :=
  match x with
  | 0 => [.empty]
  | n + 1 => do
    let q := n / 2
    let r := n % 2
    let indices := List.range (q + r + 1) |>.drop q
    let i ← indices
    let left ← cbalTree i
    let right ← cbalTree (n - i)
    pure (BinTree.node () left right)

-- `cbalTree` で生成される木の数を確認
#guard (cbalTree 3).length = 1
#guard (cbalTree 4).length = 4
#guard (cbalTree 7).length = 1

-- `cbalTree` で生成される木はすべて完全平衡
#guard (cbalTree 3)|>.map BinTree.isCBalanced |>.and
#guard (cbalTree 4)|>.map BinTree.isCBalanced |>.and
#guard (cbalTree 5)|>.map BinTree.isCBalanced |>.and
#guard (cbalTree 6)|>.map BinTree.isCBalanced |>.and