Structural Induction Example - Binary Trees

As a further example of structural induction, we consider an example worked out in detail on the domain of full binary trees.

First consider the following GRAIL grammar for full binary trees.

<tree>  ::=  <leaf>| <node> 
<leaf> ::=  <leaf-sym:symbolic-atom>
<node>  ::=  ( <left-tree:tree> <right-tree:tree> )
Example trees from this grammar include:
A
(A B)
((A B) C)
((A B) (C D))
Note that interior nodes always have two subtrees.

Now consider the following two functions for counting, respectively, the leaves and nodes of a <tree>

leaf-count[T] =
  [leaf-p[T] --> 1;
   otherwise --> 
     plus[leaf-count[left-tree[T]];
          leaf-count[right-tree[T]]]]


node-count[T] =
  [leaf-p[T] --> 0;
   otherwise --> 
     plus[1; plus[node-count[left-tree[T]];
             node-count[right-tree[T]]]]]
These are pretty straightforward functions.

The Property to Be Proven

Given these functions, we now consider proof of the following property.
leaf-count[T] = node-count[T] + 1
We want to show that this property holds for all trees T.

Inductive Definition of Binary Trees

Whenever we consider a proof by structural induction, it is based on an inductive definition of the data domain. In this case, the data domain is defined by the GRAIL grammar above. Objects of the domain are generated by the functions make-leaf and make-node. From the recursive structure of the grammar, we get the following inductive definition.

Base Rule.
For any symbolic atom x, make-leaf[x] is a binary tree.
Inductive Rule.
For any binary trees t1 and t2, make-node[t1; t2] is a binary tree.
Completeness Rule.
No objects are binary trees other than those that may be generated by the above base and inductive rules.

Inductive Proof Procedure for Binary Trees

Whenever we have an inductive definition of a data domain, we can define an analagous proof procedure. Following the approach previously illustrated for algebraic expressions and lists, we develop the proof procedure for binary trees.

To prove a property P(T) for any binary tree T, proceed as follows.

Base Step.
Prove P(make-leaf[x]) is true for any symbolic atom x.
Inductive Step.
Assume that P(t1) and P(t2) are true for arbitrary binary trees t1 and t2. Show that P(make-node[t1; t2]) is true.

Semantic Axioms for Binary Trees

Whenever proofs about the objects of an ADT are generated, those proofs typically use semantic axioms of the data domain. The semantic axioms describe the relationships between selector, recognizer, constructor and converter functions. In particular, since the domain is inductively defined in terms of the functions that generate domain objects (make-leaf and make-node), the semantics of the other functions on the domain must be specified in terms of these ones. Here are the axioms.

These axioms can now be freely used in the formal proof procedure.

Proof of the Property

Now we are ready to proceed with proof of the property
leaf-count[T] = node-count[T] + 1

Base Step.
Prove leaf-count[make-leaf[x]] = node-count[make-leaf[x]] + 1 is true for any symbolic atom x. Strategy: proceed by symbolic evaluation of both sides.
leaf-count[make-leaf[x]]
 =  [leaf-p[make-leaf[x]] --> 1;
     otherwise --> 
       plus[leaf-count[left-tree[make-leaf[x]]];
            leaf-count[right-tree[make-leaf[x]]]]]
Symbolic evaluation of leaf-count.
= 1Application of the axiom leaf-p[make-leaf[x]] = T.
node-count[make-leaf[x]] + 1
 = [leaf-p[make-leaf[x]] --> 0;
    otherwise --> 
      plus[1; plus[node-count[left-tree[make-leaf[x]]];
              node-count[right-tree[make-leaf[x]]]]]]
Symbolic evaluation of node-count.
= 0 + 1Application of the axiom leaf-p[make-leaf[x]] = T.
= 1Addition.

The two sides have been shown to be equal by symbolic evaluation. Note that we have shown the symbolic evaluation steps in full detail, by copying over conditional expressions.

The base step is complete.

Inductive Step.
Assume that leaf-count[t1] = node-count[t1] + 1 and leaf-count[t2] = node-count[t2] + 1 are true for arbitrary binary trees. Show that leaf-count[make-node[t1; t2]] = node-count[make-node[t1; t2]] + 1 is true.

Again, we proceed by symbolic evaluation of both sides.
leaf-count[make-node[t1; t2]]
 =  [leaf-p[make-node[t1; t2]]] --> 1;
     otherwise --> 
       plus[leaf-count[left-tree[make-node[t1; t2]]];
            leaf-count[right-tree[make-node[t1; t2]]]]]
Symbolic evaluation of leaf-count.
 = plus[leaf-count[left-tree[make-node[t1; t2]];
        leaf-count[right-tree[make-node[t1; t2]]]]
Application of the axiom leaf-p[make-node[t1; t2]]] = F.
 = plus[leaf-count[t1];
        leaf-count[t2]]
Application of the axioms left-tree[make-node[t1; t2]] = t1 and right-tree[make-node[t1; t2]] = t2.
 = leaf-count[t1] + leaf-count[t2]
Definition of plus.
 = node-count[t1] + 1 + node-count[t2] + 1
Inductive hypothesis.
node-count[make-node[t1; t2]] + 1
 = plus[1; plus[node-count
                 [left-tree[make-node[t1; t2]]]];
                node-count
                 [right-tree[make-node[t1; t2]]]]]]]
Symbolic evaluation of node-count and application of the axiom leaf-p[make-node[t1; t2]] = F.
 = plus[1; plus[node-count[t1];
              node-count[t2]]
Application of the axioms left-tree[make-node[t1; t2]] = t1 and right-tree[make-node[t1; t2]] = t2.
 = node-count[t1] + node-count[t2] + 1
Definition of plus.
Comparing both sides, we see that

leaf-count[make-node[t1; t2]] = node-count[make-node[t1; t2]] + 1
is true.
This completes the proof by structural induction.

Remarks

This example shows the mechanics of proofs by structural induction for recursive functional programs in all their gory detail. The main point is to convince you that such proofs of properties of programs can be done. Once you understand the method, you can have much greater confidence in your ability to program and analyze recursions. You will also have a basis for verbally discussing the correctness of programs.