A tree is a hierarchical data structure: an
element of a tree is located below some elements and above
some other elements.
Here is an example of a tree holding letters:
tree ---- j <-- root / \ f k / \ \ a h z <-- leaves
Despite the hierarchical order of the structure of the tree, the order enforced on objects in the tree will depend on how we use the tree. This just means that unlike a stack whose operations are usually limited to push and pop, there are many different kinds of trees and ways to use them. Thus, this flexibility makes them more akin to linked lists.file system ----------- / <-- root / \ ... home / \ ugrad course / / | \ ... cs101 cs112 cs113
For example, let's look at our tree of letters and examine the part starting at f and everything under it...
In this subtree, f is a root.tree ---- j / \ f k / \ \ a h z
A tree whose elements have at most 2 children is called a binary tree.
Since each element in a binary tree can have only 2 children, we typically name them the left and right child.
Since we want to be able to represent a tree in C++, how are
we going to store this hierarchical structure?
Similarly to a linked list, we can have a node that contains data and two pointers: to the left child and to the right child. If a node does not have a right or a left child, the respective pointer is NULL.
So, our first example tree of letters would look something like:
Also, just as we need a pointer to the first node to keep track of a linked list; here, we need a pointer to the root node to keep track of a tree.| v ----- | j | |---| | | | /---\ v v ----- ----- | f | | k | |---| |---| | | | |0| | /---\ ----\ v v v ----- ----- ----- | a | | h | | z | |---| |---| |---| |0|0| |0|0| |0|0| ----- ----- -----
Implementing a tree in C++ is a tricky design problem: it
is hard (if at all possible) to come up with a structure that's simple
and convenient at the same time.
The model that we consider here is fairly straightforward. However, as we will see, it does not quite satisfy the recursive property that we have stated above: "a subtree of a tree is itself a tree". This causes some problems in writing recursive functions for this model.
Our approach is to have two separate classes:
The class BTree has a pointer to the root node of the tree, which is NULL if the tree is empty.class BTNode { public: ... private: ItemType data; BTNode *lChild; BTNode *rChild; };
class BTree { public: ... private: BTNode* rootPtr; };
Class BTNode has the following methods:
The destructor is needed, since memory for the children is allocated dynamically, and therefore needs to be de-allocated when a node gets out of scope.class BTNode { public: // Constructors. BTNode(); BTNode(ItemType); // Destructor ~BTNode(); // Get fields of node. ItemType getData() const; BTNode *getLeftChild() const; BTNode *getRightChild() const; // Set fields of node. void setData(ItemType); void setLeftChild(BTNode *); void setRightChild(BTNode *); private: ItemType data; BTNode *lChild; BTNode *rChild; };
The methods for the class BTree are as follows:
There may be more methods defined for a tree, s.a. various traversals, inserting a node, etc., but for this lab we limit ourselves to the methods given above.class BTree{ public: // tree destructor ~BTree(); // check if the tree is empty bool isEmpty(); // set the value of the root void setRoot(BTNode *); // get the value of the root BTNode *getRoot(); private: BTNode *rootPtr; };
will be stored astree ---- j / \ f k / \ \ a h z
In fact, it is stored in the file data2.[j[f[a[][]][h[][]]][k[][z[][]]]]
Exercise In the file treetest.cpp write and test the functions described below. Write these functions one-by-one, compile and test your program before starting the other one.
Note that each tree function has an auxiliary function for nodes that performs a recursive call. Important: Make sure that all the functions that use trees as arguments are call-by-reference. If a tree is passed by value, it gets out of scope, and its destructor is called.