Introduction to Discrete Mathematics

Mathematical Induction

Notes By Pr. El Hadiq Zouhair

1 / 13

Overview

Python / SymPy — imports (run once) from sympy import symbols, Sum, simplify, factorial, log, oo, Rational from functools import lru_cache import math k, n, i, j = symbols('k n i j', integer=True, positive=True)
2 / 13

Principle of Induction

The truth of an infinite sequence of propositions $p_i$ for $i = b, b+1, b+2, \dots, \infty$ is established if:

$b,\; b+1,\; b+2,\; \dots$

This works because $\mathbb{N}$ has the well-ordering property: every non-empty subset has a least element. So if $p_n$ failed somewhere, there would be a smallest failure $n_0 > b$, but then $p_{n_0 - 1}$ holds and by the inductive step $p_{n_0}$ must hold — contradiction.

Domino metaphor: if the first domino falls (base) and each falling domino knocks over the next (step), the whole chain falls.
3 / 13

Method

Inductive proofs always have the same three-step structure, making them easy to follow:

BSBase step — verify that $p_b$ is true (usually $b = 0$ or $1$).
IHInductive hypothesis — assume $p_i$ is true for all $b \le i \le k$.
ISInductive step — prove $p_k \Rightarrow p_{k+1}$, equivalently $(a_k \Rightarrow b_k) \Rightarrow (a_{k+1} \Rightarrow b_{k+1})$.

Tactical pattern for the inductive step:

Python — generic induction skeleton (numerical sanity check) def check_by_induction(prop, n_min=1, n_max=100): """prop(n) must return True. Tests that prop holds on [n_min, n_max].""" for n in range(n_min, n_max + 1): if not prop(n): return False, n return True, None # Example: prop(n) := sum(1..n) == n*(n+1)//2 ok, witness = check_by_induction(lambda n: sum(range(1, n+1)) == n*(n+1)//2) print(ok) # True
4 / 13

Classic example — $\sum_{i=1}^{n} i = \dfrac{n(n+1)}{2}$

BS$n = 1$:   LHS $= 1$,   RHS $= \dfrac{1 \cdot 2}{2} = 1$. ✓
IHAssume $\displaystyle\sum_{i=1}^{k} i = \dfrac{k(k+1)}{2}$.
IS $\displaystyle\sum_{i=1}^{k+1} i = \underbrace{\sum_{i=1}^{k} i}_{= k(k+1)/2 \text{ by IH}} + (k+1) = \dfrac{k(k+1) + 2(k+1)}{2} = \dfrac{(k+1)(k+2)}{2}.\;\square$
Python — empirical for n in range(1, 1001): assert sum(range(1, n+1)) == n*(n+1)//2 print("Identity verified on [1, 1000] ✓")
SymPy — symbolic from sympy import symbols, Sum, simplify i, n = symbols('i n', integer=True, positive=True) print(simplify(Sum(i, (i, 1, n)).doit())) # n*(n+1)/2
5 / 13

Set example — a set of $n$ elements has $2^n$ subsets

BS$n = 0$: the empty set has only one subset — itself, $\varnothing$. So $|\mathcal{P}(\varnothing)| = 1 = 2^0$. ✓
IHAssume a set of $i$ elements has $2^i$ subsets, for all $i \leq k$.
ISConsider a set $S$ with $k + 1$ elements. Pick one element $e \in S$ and split the subsets of $S$ in two: Total: $2 \cdot 2^k = 2^{k+1}$. $\square$
Python — empirical for small n from itertools import chain, combinations def powerset(s): s = list(s) return list(chain.from_iterable(combinations(s, r) for r in range(len(s)+1))) for n in range(0, 8): S = set(range(n)) assert len(powerset(S)) == 2 ** n print("|P(S)| = 2^|S| verified for n in [0, 7] ✓")
6 / 13

Growth example — $2^n < n!$ for $n \geq 4$

BS$n = 4$: $2^4 = 16$, $\;4! = 24$.   $16 < 24$. ✓
IHAssume $2^i < i!$ for some $i \geq 4$.
IS $2^{i+1} = 2 \cdot 2^i \;\underset{\text{IH}}{<}\; 2 \cdot i! \;\underset{i \geq 4}{<}\; (i+1) \cdot i! = (i+1)!.\;\square$
Python — check & visualize import math for n in range(4, 21): assert 2**n < math.factorial(n) print("2^n < n! on [4, 20] ✓") for n in range(1, 11): print(f"{n:>2}: 2^n = {2**n:>10} n! = {math.factorial(n):>10}")
SymPy — asymptotic growth from sympy import symbols, factorial, limit, oo n = symbols('n', positive=True, integer=True) print(limit(factorial(n) / 2**n, n, oo)) # oo # factorial grows faster than exponential
7 / 13

Real example — $\ln(n) \leq \displaystyle\sum_{i=1}^{n} \dfrac{1}{i}$

BS$n = 1$: $\ln 1 = 0 \leq 1 = \dfrac{1}{1}$. ✓
IHAssume $\ln(j) \leq \displaystyle\sum_{i=1}^{j} \dfrac{1}{i}$ for all $j \leq k$.
ISSince $\ln(k+1) - \ln(k) \leq \dfrac{1}{k+1}$ for $k \geq 1$ (concavity of $\ln$), $\displaystyle\sum_{i=1}^{k+1} \dfrac{1}{i} = \dfrac{1}{k+1} + \underbrace{\sum_{i=1}^{k} \dfrac{1}{i}}_{\geq \ln(k)} \;\geq\; \dfrac{1}{k+1} + \ln(k) \;\geq\; \ln(k+1).\;\square$
Python — numerical check import math for n in range(1, 1001): H = sum(1/i for i in range(1, n+1)) # n-th harmonic number assert math.log(n) <= H + 1e-12 print("ln(n) ≤ H_n on [1, 1000] ✓")

Equivalently $H_n = \sum_{i=1}^n 1/i$ behaves like $\ln(n) + \gamma$ for large $n$ — the Euler–Mascheroni constant.

8 / 13

Strong Induction

Same goal: establish $p_b, p_{b+1}, \dots, p_\infty$. The difference is in the inductive step:

BSVerify that $p_b$ is true.
IHAssume that $p_i$ is true for all $b \leq i \leq k$.
ISProve $\bigl(p_b \wedge p_{b+1} \wedge p_{b+2} \wedge \dots \wedge p_k\bigr) \Rightarrow p_{k+1}$.

Strong induction uses the full history of previous cases, not just the immediate predecessor. It is essential when the recurrence reaches back several steps (e.g. $a_n = a_{n-1} + a_{n-2}$, or proofs that split the object into two smaller pieces of unknown size).

Equivalence: strong induction and (weak) induction are logically equivalent over $\mathbb{N}$. Strong induction is just more convenient when the natural argument refers to several smaller cases.

9 / 13

Geometry example — triangulating a polygon

Lemma: every simple polygon with at least four sides has an interior diagonal.

Theorem (by strong induction): a simple polygon with $n$ sides ($n \geq 3$) can be triangulated into exactly $n - 2$ triangles.

BS$n = 3$ (a triangle): $3 - 2 = 1$ triangle. ✓
IHAll simple polygons of size $i$ can be triangulated into $i - 2$ triangles, $\forall\,3 \leq i \leq k$.
ISConsider a polygon with $k + 1$ sides, $k \geq 3$, so $k + 1 \geq 4$.
10 / 13

Programming connection — recursion ↔ induction

Inductive proofs are most intuitive when applied to recursive definitions. Every recursive function corresponds to an inductive structure:

Python — factorial as recursive (= inductive) definition from functools import lru_cache @lru_cache def fact(n): if n == 0: # BASE STEP return 1 return n * fact(n-1) # INDUCTIVE STEP (uses fact(n-1) which is the IH) print([fact(k) for k in range(7)]) # [1, 1, 2, 6, 24, 120, 720]
Python — Fibonacci needs STRONG induction (depends on 2 previous values) @lru_cache def fib(n): if n < 2: # BASE STEP for n = 0, 1 return n return fib(n-1) + fib(n-2) # uses TWO previous IHs print([fib(k) for k in range(10)]) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Loop invariants in algorithm analysis are proven by induction on the loop counter. This is how correctness proofs of algorithms (binary search, merge sort, …) work.

11 / 13

Common pitfalls

Python — empirical evidence is not a proof from sympy import isprime first_fail = next(n for n in range(1000) if not isprime(n*n + n + 41)) print(first_fail) # 40 (= 1681 = 41 * 41)
12 / 13

Summary

Python — key tools for this lesson import math from functools import lru_cache from itertools import chain, combinations from sympy import symbols, Sum, simplify, factorial, log, limit, oo, Rational, isprime
13 / 13