Eleanor / | \ / | \ David Daniel Judy / | | \ / | | \ Ellen Maggie Andrew Michael | | Ben
David / | / | Ellen Maggie | | Benis a subtree of the family tree (above).
template <class Item> class BinaryTree { public: ... private: Item val; BinaryTree *left; BinaryTree *right; };
template <class Item> class BinaryTree { public: ... // Constant member functions size_t height() const; size_t size() const; bool isLeaf() const; BinaryTree *leftSubtree() const; BinaryTree *rightSubtree() const; // Friend functions friend BinaryTree *merge(Item v, BinaryTree *L, BinaryTree *R); private: ... };
template <class Item> class BinaryTree { public: // Constructors BinaryTree(Item v); // create a leaf consisting of v BinaryTree(const BinaryTree& source); // copy constructor // Destructor ~BinaryTree(); // Modification member functions void operator=(const BinaryTree& source); // assignment op ... private: ... }
BinaryTree<int> *B = new BinaryTree<int>(8); // B --> (8) BinaryTree<int> *C = new BinaryTree<int>(5); // C --> (5) BinaryTree<int> *D = new BinaryTree<int>(2); // D --> (2) BinaryTree<int> *E = merge(4, D, C); // E --> (4) // / \ // (2) (5) BinaryTree<int> *F = merge(6, E, B); // F --> (6) // / \ // (4) (8) // / \ // (2) (5)
template <class Item> BinaryTree<Item>::BinaryTree(Item v) // create a leaf consisting of v : val(v), left(NULL), right(NULL) { }
template <class Item> bool BinaryTree<Item>::isLeaf() const { return (left==NULL && right==NULL); } template <class Item> BinaryTree<Item> *BinaryTree<Item>::leftSubtree() const { return left; } template <class Item> BinaryTree<Item> *BinaryTree<Item>::rightSubtree() const { return right; }
template <class Item> size_t BinaryTree<Item>::size() const { if (isLeaf()) return 1; else if (left==NULL) return (1 + right->size()); else if (right==NULL) return (1 + left->size()); else return (1 + left->size() + right->size()); }
template <class Item> size_t BinaryTree<Item>::height() const { if (isLeaf()) return 0; else if (left==NULL) return (1 + right->height()); else if (right==NULL) return (1 + left->height()); else return (1 + max(left->height(), right->height())); }
template <class Item> BinaryTree<Item> *merge(Item v, BinaryTree<Item> *L, BinaryTree<Item> *R) // a friend of class BinaryTree<Item< { BinaryTree<Item> *T = new BinaryTree<Item>(v); // start with a leaf T->left = L; T->right = R; return T; }
BinaryTree<int> *C = new BinaryTree<int>(5); BinaryTree<int> *D = new BinaryTree<int>(2); BinaryTree<int> *E = merge(4, D, C); BinaryTree<int> *G = merge(6, E, C); // G --> (6) // / \ // (4) (8) // / \ / // (2) (5)C (5) no longer has a unique parent so this is not a real tree. (It is an example of something we will see later known as a directed acyclic graph (DAG).)
A tree traversal is the process of iterating over the value stored at each node in a tree exactly once. In the process you may actually pass through a node more than once, but the action is performed on the value of the node only one time. When the action is performed largely determines the order of traversal. Sometimes performing the action at a node is referred to as visiting.
(8) / \ (2) (21) / \ / (1) (5) (13) / (3) pre-order: 8, 2, 1, 5, 3, 21, 13 post-order: 1, 3, 5, 2, 13, 21, 8 in-order: 1, 2, 3, 5, 8, 13, 21
Traversals need be performed recursively - we use the recursion to allow us to backtrack up the tree without repeating ourselves.
Before looking at a C++ implementation of the traversals, let's rethink our BinaryTree class. Examine the interface of the binary tree class template here.
Changes from our previous attempt:BinaryTree(Item v, BinaryTree<Item> *L, BinaryTree<Item> *R);
We add a depth functon to compute the depth of any node in a tree. (Notice how we can think of a node as a tree and vice versa. Think about why this is so.)
How do we implement depth? We can use recursion: if a node has no parent (it's the root) then it has depth 0 otherwise the depth is 1 + depth of the node's parent.BinaryTree<Item> *parentOf() const;
We do not add any function to update the parent of a node --- such a function is unnecessary as the parent of a node is changed by using the merge constructor, setLeft, or setRight.
Now, depth can be defined recursively:template <class Item> size_t BinaryTree<Item>::depth() const { if (parent==NULL) return 0; else return (1 + parent->depth()); }
template <class Item> void inOrderPrint(BinaryTree<Item> *bt) { if (bt != NULL) { inOrderPrint(bt->leftOf()); cout << bt->valOf() << "\n"; inOrderPrint(bt->rightOf()); } }
Of course, we might want to have related functions that print the values in-order, but slightly differently (perhaps no carriage return). Or we might want to do an in-order traversal that does an entirely different action --- like put the elements into a global array. We know how to generalize the in-order traversal. We can use function pointers as in:
template <class Item> void inOrderWalk(BinaryTree<Item> *bt, void (*f)(Item)) { if (bt != NULL) { inOrderWalk(bt->leftOf(), f); f(bt->valOf()); inOrderWalk(bt->rightOf(),f); } }For example, for a BinaryTree<int> we could achieve the same functionality as inOrderPrint by defining a function:
void print(int x) { cout << x << "\n"; }and then for a binary tree *t, calling inOrderWalk(t, print). However, there is a limitation to the generality of this method --- we cannot now decide to use a function argument that takes an argument that is a reference to an Item. We'd like to be able to do that as well. We can achieve that by adding a template parameter:
template <class Item, class Func> void inOrderWalk(BinaryTree<Item> *bt, Func f) { if (bt != NULL) { inOrderWalk(bt->leftOf(), f); f(bt->valOf()); inOrderWalk(bt->rightOf(),f); } }Now we can use any function as an argument to this function that can be called that fits the form f(bt->valOf()). This code as well as code performing general pre-order and post-order traversals can be found here.