Introduction
Linked Lists have great advantages of flexibility over the contiguous representation of data structure, but they have one weakness: They are sequential lists; that is, they are arranged so that it is necessary to move through them only one position at a time. In this post, we can overcome this problem by using trees as data structure, with methods of pointers.
Definition
A binary tree is either empty, or it consists of a node called the root together with two binary trees called the left subtree and the right subtree of the root.
Noted that binary trees are not the same class as the 2-trees. Each node in a 2-tree has either 0 or 2 children, never 1, as can happen with a binary tree.
For n nodes, here I give a formular to calculate the number of different binary trees:
$$ \cfrac {C_{2n}^{n}}{n+1} $$
Tree Traversal
What is the order?
Traversal, moving through all the nodes of the binary tree, visiting each node in turn, is one of the most important operations on a binary tree.
For lists or other data structures we’ve mentioned previously, the traversal operation follows the same order. However, for trees, there are several orders in which we could traverse all the nodes.
At a given node, then, there are three tasks we shall wish to do in some order: We shall visit the node itself, we shall traverse its *left subtree**; and we shall traverse its right subtree. The key distinction in traversal orders is to decide which node we should visit first, among the node itself, its left subtree, and its right subtree.
Standard Traversal Orders
For a traversal task, we denote that a node V, its left subtree L, and its right subtree R. So there are 3 orders we may have:
preorder | inorder | postorder |
---|---|---|
VLR | LVR | LRV |
Expression Trees
An expression tree is built up from the simple operands and operators of an(arithmetical or logical) expression by placing the simple operands as the leaves of a binary tree and the operators as the interior nodes.
For each binary operator, the left subtree contains all the simple operands and operators in the left operand of the given operator, and the right subtree contains everything in the right operand.
For a unary operator, one of the two subtrees will be empty.
Polish Form
The names of the traversal methods are related to the Polish forms of the expression. Traversal of an expression tree in preorder yields the prefix form, in which every operator is written before its operands; inorder traversal gives the infix form(the customary way that we use in daily life); and postorder traversal gives the postfix form, in which all operators appear after their operands. Although we may consider the infix form convenient to use, the computer prefer a postfix form expression, Reverse Polish expression to calculate. I will show you how to convert a infix form to a postfix form and calculate it in another post, but not here.
Expression | a+b | log x | n ! | a-(b×c) | (a<b) or (c<d) |
---|---|---|---|---|---|
preorder | + a b | log x | ! n | - a × b c | or < a b < c d |
inorder | a + b | log x | n ! | a - b × c | a < b or c < d |
postorder | a b + | x log | n ! | a b c × - | a b < c d < or |
Implementation
First we consider the representation of the nodes that make up a tree. We define a structure Binary_node, which has both a left and a right subtree.
1 | template <class Entry> |
And its implementations:
1 | template <class Entry> |
Now we can start to construct a tree. For convenience, here we give most methods that a tree need, but not necessary to implement them right now. This provides us a bluprint to construct any binary tree.
1 | template <class Entry> |
It seems a bit more complex but just relax. I’ll explain it in details. At first, functions like height, size, and clear are basic functions that tell us some features of a tree. They all can be implemented easily by using recursions. So they have their corresponding auxiliary functions in the protected field.
Now we move to see the traversal functions. As I’ve introducted that there are 3 standard traversal orders, there are certainly three traversal functions. And of course, they have their corresponding auxiliary functions as well.
Noted that the insert function here is used to test our basic tree class, it will be overrided when BST or other trees are implemented.
BinaryTree()
precondition: None.
postcondition: The BinaryTree has been created and is initialized to be empty.
1 | template <class Entry> |
BinaryTree(const BinaryTree< Entry > &original)
precondition: None.
postcondition: The BinaryTree has been created and is initialized with BinaryTree original.
1 | template <class Entry> |
recursive_copy(Binary_node< Entry > *sub_root)
precondition: None.
postcondition: Return a node that is initialized with sub_root.
1 | template <class Entry> |
BinaryTree& operator=(const BinaryTree< Entry > &original)
precondition: None.
postcondition: The BinaryTree is reset to copy original.
1 | template <class Entry> |
~BinaryTree()
precondition: None.
postcondition: The BinaryTree itself is released.
1 | template <class Entry> |
empty()
precondition: None.
postcondition: The function returns true or false according to whether the BinaryTree is empty or not.
1 | template <class Entry> |
size()
precondition: None.
postcondition: Return the number of entries in the BinaryTree.
1 | // 计算节点个数 |
recursive_size(Binary_node< Entry > *sub_root)
precondition: sub_root is either NULL or points to a subtree of the BinaryTree.
postcondition: Return the number of entries in the subtree rooted at sub_root.
1 | template <class Entry> |
height()
precondition: None.
postcondition: Return the height of the BinaryTree.
1 | // 计算树高 |
recursive_height(Binary_node< Entry > *sub_root)
precondition: sub_root is either NULL or points to a subtree of the BinaryTree.
postcondition: Return the height of the subtree.
1 | template <class Entry> |
width()
precondition: None.
postcondition: Return the width of the BinaryTree.
1 | template <class Entry> |
leaf_count()
precondition: None.
postcondition: Return the number of leaves in the BinaryTree.
1 | // 计算叶子节点个数 |
recursive_leaf_count(Binary_node< Entry > *sub_root)
precondition: sub_root is either NULL or points to a subtree of the BinaryTree.
postcondition: Return the number of leaves in the subtree rooted at sub_root.
1 | template <class Entry> |
clear()
precondition: None.
postcondition: Clean the BinaryTree itself.
1 | template <class Entry> |
recursive_clear(Binary_node< Entry > *&sub_root)
precondition: sub_root is either NULL or points to a subtree of the BinaryTree.
postcondition: The subtree has been cleaned.
1 | template <class Entry> |
preorder(void (*visit)(Entry &))
precondition: None.
postcondition: Traverse the BinaryTree in preorder.
1 | template <class Entry> |
recursive_preorder(Binary_node< Entry > *sub_root, void (*visit)(Entry &))
precondition: sub_root is either NULL or points to a subtree of the BinaryTree.
postcondition: The subtree has been been traversed in preorder sequence.
1 | template <class Entry> |
inorder(void (*visit)(Entry &))
precondition: None.
postcondition: Traverse the BinaryTree in inorder.
1 | template <class Entry> |
recursive_inorder(Binary_node< Entry > *sub_root, void (*visit)(Entry &))
precondition: sub_root is either NULL or points to a subtree of the BinaryTree.
postcondition: The subtree has been been traversed in inorder sequence.
1 | template <class Entry> |
postorder(void (*visit)(Entry &))
precondition: None.
postcondition: Traverse the BinaryTree in postorder.
1 | template <class Entry> |
recursive_postorder(Binary_node< Entry > *sub_root, void (*visit)(Entry &))
precondition: sub_root is either NULL or points to a subtree of the BinaryTree.
postcondition: The subtree has been been traversed in postorder sequence.
1 | template <class Entry> |
insert(const Entry &)
precondition: None.
postcondition: If the root is empty, the new entry should be inserted into the root, otherwise it should be inserted into the shorter of the two subtrees of the root(or into the left subtree if both subtrees have the same height).
1 | // 插入 |
recursive_insert(Binary_node< Entry > *&sub_root, const Entry &x)
precondition: sub_root is either NULL or points to a subtree of the BinaryTree.
postcondition: If subroot is n NULL, x is inserted to this subtree; else, move to left or right subtree.
1 | template <class Entry> |
double_traverse(void (*visit)(Entry &))
precondition: None.
postcondition: At each node of the BinaryTree, double-order traversal function first visits the node, then traverses its left subtree(in double order), then visits the node again, then traverses its right subtree(in double order).
1 | // 双重遍历 |
recursive_double_traverse(Binary_node *sub_root, void (*visit)(Entry &))
precondition: None.
postcondition: The subtree rooted at sub_root is doubly traversed.
1 | template <class Entry> |
level_traverse(void (*visit)(Entry &))
precondition: None.
postcondition: The BinaryTree is traversed level by level, starting from the top. The operation *visit is applied to all entries.
1 | template <class Entry> |
That’s the general introduction of BinaryTree. In next post, I’ll introduce another useful tree structure to you. Thank you!