Sequences, stacks, and queues, are all linear structures: in all three data structures, one item follows another. Trees will be our first non-linear structure:
Trees have many uses:
Here is the conceptual picture of a tree (of letters):
So a (computer science) tree is kind of like an upside-down real tree...
A path in a tree is a sequence of (zero or more) connected nodes; for example, here are 3 of the paths in the tree shown above:
The length of a path is the number of nodes in the path, e.g.:
The height of a tree is the length of the longest path from the root to a leaf; for the above example, the height is 4 (because the longest path from the root to a leaf is or ).
The depth of a node is the length of the path from the root to that node; for the above example:
Given two connected nodes like this:
Node A is called the parent, and node B is called the child.
A subtree of a given node includes one of its children and all of its descendants. The descendants of a node includes all reachable nodes (its children, its children's children, etc.). In the original example, there are three subtrees of A: B & D, I and C, E, F, G & J.
An important special kind of tree is the binary tree. In a binary tree:
Here are two examples of binary trees that are different:
They are different because D and E switched places --The left and right child are different. Also not that the arrows are not shown. Often the arrow is implicit from the node higher up to the lower node.
Since a binary-tree node never has more than two children, a node can be represented using a class with 3 fields: one for the data in the node, plus two child pointers:
class BinearyTreenode { Object data; BinaryTreenode leftChild; BinaryTreenode rightChild; }
However, since a general-tree node can have an arbitrary number of children, a fixed number of child-pointer fields won't work. To keep a list of children we can use a Sequence, e.g., Sequence children;. Each node in has the structure:
class Treenode { Object data; Sequence children; }
As shown before, there are two common ways to represent the sequence: array and linked-list.
For example, consider this general tree (a simplified version of the original example):
For the array representation of the Sequence (where the array has an initial size of 4) we would have:
and the linked-list representation of the Sequence we would have:
It is often useful to iterate through the nodes in a tree:
When we iterated through a sequence, we started with the first node and visited each node in turn. Since each node is visited, the best possible complexity is O(N) for a tree with N nodes. All of our traversal methods will achieve this complexity.
For trees, there are many different orders in which we might visit the nodes. There are three common traversal orders for general trees, and one more for binary trees: preorder, postorder, level order, and in-order, all described below. We will use the following tree to illustrate each traversal:
Preorder
A preorder traversal can be defined (recursively) as follows:
If we use a preorder traversal on the example tree given above, and we print the letter in each node when we visit that node, the following will be printed: A B D C E G F H I.
Postorder
A postorder traversal is similar to a preorder traversal, except that the root of each subtree is visited last rather than first:
If we use a postorder traversal on the example tree given above, and we print the letter in each node when we visit that node, the following will be printed: D B G E H I F C A.
Level order
The idea of a level-order traversal is to visit the root, then visit all nodes "1 level away" (depth 2) from the root (left to right), then all nodes "2 levels away" (depth 3) from the root, etc. For the example tree, the goal is to visit the nodes in the following order:
A level-order traversal requires using a queue (rather than a recursive algorithm, which implicitly uses a stack). Here's how to print the data in a tree T in level order, using a queue Q:
Q.enqueue(root) while (!Q.isEmpty()) { // show Q here n = Q.dequeue(); System.out.print(n.data); s = root.children; s.start(); try { while (!s.isCurrent() { Q.enqueue(s.getCurrent()); s.advance(); } } catch (NoCurrentException e) {} // can't happen }
Show the items in the Queue Q at the start of the outer while in the code above using our standard example for traversals. Also state the output from the code where the data is the key at the node.
In-order
An in-order traversal involves visiting the root "in between" visiting its left and right subtrees. Therefore, an in-order traversal only makes for binary trees. The (recursive) definition is:
If we print the letters in the node's of our example tree using an in-order traversal, the following will be printed: D B A E G C H F I
For the preorder, postorder and in-order traversal, the primary difference between them is where the node is visited in relation to the recursive calls, e.g., before, after or between. The general notes on recursion showed a similar effect where there was one recursive call.
What is printed when the following tree is visited using a (a) preorder traversal, (b) a postorder traversal, (c) a level-order traversal, and (d) an in-order traversal?