Balanced Binary Search Trees

March 3, 2017

We begin with the same binary search tree code we wrote in a recent exercise:

(define (insert lt? x tree)
  (cond ((null? tree)
          (list x (list) (list)))
        ((lt? x (car tree))
          (list (car tree) (insert lt? x (cadr tree)) (caddr tree)))
        ((lt? (car tree) x)
          (list (car tree) (cadr tree) (insert lt? x (caddr tree))))
        (else tree)))
(define (member? lt? x tree)
  (cond ((null? tree) #f)
        ((lt? x (car tree)) (member? lt? x (cadr tree)))
        ((lt? (car tree) x) (member? lt? x (caddr tree)))
        (else #t)))

A binary search tree is balanced if the depths of its two children are the same, and if both of its two children are balanced. We are assuming here perfect balance, so that only trees with 2n−1 nodes for integer n can possibly be balanced:

(define (depth tree)
  (if (null? tree) 0
    (+ 1 (max (depth (cadr tree))
              (depth (caddr tree))))))
(define (balanced? tree)
  (if (null? tree) #t
    (and (= (depth (cadr tree))
            (depth (caddr tree)))
         (balanced? (cadr tree))
         (balanced? (caddr tree)))))

Here are two examples:

> (balanced? (insert < 4 (insert < 6 (insert < 0 (insert < 7
    (insert < 1 (insert < 5 (insert < 2 (insert < 3 (list))))))))))
#f
> (balanced? (insert < 0 (insert < 6 (insert < 4 (insert < 2
    (insert < 1 (insert < 5 (insert < 3 (list)))))))))
#t

There’s a lot of extra work going on there. Both functions use the same recursion, so they could be flattened into a single function. And both functions recompute previously-computed depths in their recursive calls; a more clever function could order its computations to calculate the depths only once. An even more clever function never even computes the depth: a tree is balanced if it is null, or if both its children are null, or if both its children are non-null and are balanced:

(define (balanced? tree)
  (or (null? tree)
      (and (null? (cadr tree)) (null? (caddr tree)))
      (and (not (null? (cadr tree))) (not (null? (caddr tree)))
           (balanced? (cadr tree)) (balanced? (caddr tree)))))

Here are the same two examples:

> (balanced? (insert < 4 (insert < 6 (insert < 0 (insert < 7
    (insert < 1 (insert < 5 (insert < 2 (insert < 3 (list))))))))))
#f
> (balanced? (insert < 0 (insert < 6 (insert < 4 (insert < 2
    (insert < 1 (insert < 5 (insert < 3 (list)))))))))
#t

You can run the program at http://ideone.com/JFq9dE. I wonder if CBP agents speak Scheme?

Advertisements

Pages: 1 2

5 Responses to “Balanced Binary Search Trees”

  1. Jussi Piitulainen said

    Untested, of course. I’m trying to simulate my being in the situation of that engineer :(

    # assume: tree is None or left(tree), right(tree) are trees                     
    
    def dep(tree):
        '''Return the depth of the tree if it's balanced, None if it's not.         
                                                                                    
        '''
        if tree is None: return 0
        west = dep(left(tree))
        if west is None: return None
        east = dep(right(tree))
        if east is None: return None
        if -1 <= west - east <= 1: return 1 + max(west, east)
        return None
    
    def isbalanced(tree): return dep(tree) is not None
    
  2. [sourcelang=”css”]

    def makeNode(left=None, right=None):
    (left, right)
    def left(node):
    node[0]
    def right(node):
    node[1]

    left = makeNode(left=makeNode(left=makeNode(left=makeNode(left=makeNode()))))
    print(“{} is {}balanced.”.format(‘left’,”” if isbalanced(left) else “not “))

    left is balanced.

    # good job Python!
    [/sourcelang=”css”]

    In Common Lisp:

    
    (defgeneric balancedp (tree)
      (:documentation "Return nil if the tree is not balanced, or its number of nodes if it is.")
      (:method ((node t))    0)
      (:method ((node node))
        (let ((lc (balancedp (node-left  node)))
              (rc (balancedp (node-right node))))
          (and lc
               rc
               (<= (abs (- lc rc)) 1)
               (+ 1 lc rc)))))
    
    (assert (balancedp nil))
    (assert (balancedp (make-node)))
    (assert (balancedp (make-node :left (make-node))))
    (assert (not (balancedp (make-node :left (make-node :right (make-node))))))
    (assert (balancedp (make-node :left (make-node :right (make-node))
                                  :right (make-node :left (make-node)))))
    (assert (not  (balancedp (make-node :left (make-node  :left (make-node)
                                                          :right (make-node))
                                        :right (make-node :left (make-node  :left (make-node)))))))
    
  3. Jussi Piitulainen said

    Python functions need to have return statements. They return None by default.

  4. […] a recent exercise we wrote a program to determine if a binary tree was balanced. Today’s exercise is […]

  5. A C++ solution that checks for near perfect balance.
    [sourcelang=”c++”]

    struct Node
    {
    int key;
    Node * left, * right;

    Node(int k)
    {
    key = k;
    left = right = NULL;
    }
    };

    int height(Node * node)
    {
    if(node == NULL)
    return 0;

    return max( height(node->left), height(node->right));

    }

    bool IsBalanced(Node * node)
    {
    int diff = height(node->left) – height(node->right);
    return ( -1 <= diff && diff left) && IsBalanced(node->right));

    }

    [/sourcelang=”c++”]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: