Workspace IndexMath › Day 4

Induction and Structural Recursion TODO

Math · Day 4 / 52 · July — Discrete Math & Logic (Day 3-10)

Concept

Mathematical induction is a proof principle grounded in the well-ordering of the natural numbers: showing a base case and a step from n to n+1 lets you conclude the proposition holds for all natural numbers. Strong induction lets you use every case smaller than n as a hypothesis, and generalizing this further gives well-founded induction over well-founded relations, which applies to structures beyond the naturals. Structural induction is a special case of this that uses the fact that algebraic data types are built up finitely from constructors, proving the proposition for each constructor. For trees, you handle the leaf (base case) and the internal node (conclusion from the hypothesis on the children) separately, and this proof structure matches exactly the shape of a recursive function. In other words, proving a recursive program correct is structural induction that follows the program's own recursive structure, and termination is shown by exhibiting a well-founded measure that decreases on every recursive call.

Being able to explain why an invariant of a recursive function or tree data structure always holds — rather than just "I ran it and it worked" — lets you rule out edge cases by structure instead of by testing.

Code & Formula

# 귀납법/구조적 재귀 — 이진 트리의 "리프 수 = 내부노드 수 + 1"을 구조적 귀납으로 확인.
# 기저: 리프 하나뿐인 트리는 leaves=1, internal=0 → 1=0+1.
# 귀납 단계: 왼쪽/오른쪽 부분트리가 각각 성립한다고 가정하면, 합쳐도 그대로 성립.

class Leaf:
    pass

class Node:
    def __init__(self, left, right):
        self.left = left
        self.right = right

def count_leaves(t):
    if isinstance(t, Leaf):
        return 1
    return count_leaves(t.left) + count_leaves(t.right)   # 재귀 = 귀납 가정 사용

def count_internal(t):
    if isinstance(t, Leaf):
        return 0
    return 1 + count_internal(t.left) + count_internal(t.right)

def check_property(t):
    # 명제 P(t): leaves(t) == internal(t) + 1
    return count_leaves(t) == count_internal(t) + 1

# 무작위로 완전(full) 이진 트리를 만들어 매번 P(t)가 성립하는지 확인.
import random
def random_full_tree(depth):
    if depth == 0 or random.random() < 0.3:
        return Leaf()
    return Node(random_full_tree(depth - 1), random_full_tree(depth - 1))

random.seed(0)
trees = [random_full_tree(4) for _ in range(20)]
results = [check_property(t) for t in trees]

print("검사한 트리 수:", len(trees))
print("모두 P(t) 성립?", all(results))
sample = trees[0]
print(f"예시 트리: 리프={count_leaves(sample)}, 내부노드={count_internal(sample)}")

Exercise

Prove on paper, by structural induction, that a (full) binary tree always has exactly one more leaf than internal node, then write a verification function recursively following the same structure.

Practical Connection

Properties of Merkle trees like "equal roots imply equal leaf sets" or "inclusion-proof verification is correct" are all proved by induction on tree height, and the recursive structure of the verification code corresponds one-to-one with the induction step.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/math-50-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

귀납법/구조적 재귀 TODO

Math · Day 4 / 52 · 7월 — 이산수학·논리 (Day 3–10)

트리 성질을 귀납으로 "설명"

개념

수학적 귀납법은 자연수 집합의 정렬성(well-ordering)에 기반한 증명 원리로, 기저 사례와 "n에서 n+1로 넘어간다"는 단계를 보이면 모든 자연수에 대해 명제가 성립함을 결론짓는다. 강한 귀납법은 n보다 작은 모든 경우를 가정에 쓸 수 있게 한 형태이고, 이를 일반화하면 well-founded 관계 위의 정초 귀납법이 되어 자연수가 아닌 구조에도 적용된다. 구조적 귀납법은 그 특수형으로, 대수적 자료형이 생성자로부터 유한하게 만들어진다는 사실을 이용해 각 생성자마다 명제를 보이는 방식이다. 트리에 대해서는 리프(기저)와 내부 노드(자식들의 가정으로부터 결론)를 각각 처리하면 되고, 이 증명 구조는 그대로 재귀 함수의 형태와 일치한다. 즉 재귀 프로그램의 정당성 증명은 그 프로그램의 재귀 구조를 따라가는 구조적 귀납법이며, 종료성은 재귀 호출마다 감소하는 well-founded 척도를 제시해 보인다.

재귀 함수나 트리 자료구조의 불변식을 "돌려 보니 되더라"가 아니라 왜 항상 성립하는지로 설명할 수 있어야, 엣지 케이스를 테스트가 아니라 구조로 걸러낼 수 있다.

코드 · 수식

# 귀납법/구조적 재귀 — 이진 트리의 "리프 수 = 내부노드 수 + 1"을 구조적 귀납으로 확인.
# 기저: 리프 하나뿐인 트리는 leaves=1, internal=0 → 1=0+1.
# 귀납 단계: 왼쪽/오른쪽 부분트리가 각각 성립한다고 가정하면, 합쳐도 그대로 성립.

class Leaf:
    pass

class Node:
    def __init__(self, left, right):
        self.left = left
        self.right = right

def count_leaves(t):
    if isinstance(t, Leaf):
        return 1
    return count_leaves(t.left) + count_leaves(t.right)   # 재귀 = 귀납 가정 사용

def count_internal(t):
    if isinstance(t, Leaf):
        return 0
    return 1 + count_internal(t.left) + count_internal(t.right)

def check_property(t):
    # 명제 P(t): leaves(t) == internal(t) + 1
    return count_leaves(t) == count_internal(t) + 1

# 무작위로 완전(full) 이진 트리를 만들어 매번 P(t)가 성립하는지 확인.
import random
def random_full_tree(depth):
    if depth == 0 or random.random() < 0.3:
        return Leaf()
    return Node(random_full_tree(depth - 1), random_full_tree(depth - 1))

random.seed(0)
trees = [random_full_tree(4) for _ in range(20)]
results = [check_property(t) for t in trees]

print("검사한 트리 수:", len(trees))
print("모두 P(t) 성립?", all(results))
sample = trees[0]
print(f"예시 트리: 리프={count_leaves(sample)}, 내부노드={count_internal(sample)}")

연습

이진 트리의 리프 개수가 (완전 이진 트리에서) 내부 노드 개수보다 정확히 하나 많다는 명제를 구조적 귀납법으로 종이에 증명하고, 같은 구조로 검증 함수를 재귀로 작성해 볼 것.

실무 · Verex 연결

Merkle 트리의 "루트가 같으면 리프 집합이 같다"거나 "포함 증명 검증이 정확하다" 같은 성질은 모두 트리 높이에 대한 귀납으로 증명되며, 증명 검증 코드의 재귀 구조가 그 귀납 단계와 일대일 대응한다.

공부한 날 원본 커리큘럼(docs/knowledge/math-50-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 3. 명제논리·집합·함수·관계5. 그래프 기초(DAG·트리·해시 링크) →