Expression Trees

advertisement

Expression Trees

Algebraic expressions such as have an inherent tree-like structure. For example, Figure is a representation of the expression in Equation . This kind of tree is called an expression tree .

The terminal nodes (leaves) of an expression tree are the variables or constants in the expression ( a , b , c , d , and e ). The non-terminal nodes of an expression tree are the operators (+, -, , and ). Notice that the parentheses which appear in Equation do not appear in the tree. Nevertheless, the tree representation has captured the intent of the parentheses since the subtraction is lower in the tree than the multiplication.

Figure: Tree representing the expression a / b +( c d ) e .

The common algebraic operators are either unary or binary. For example, addition, subtraction, multiplication, and division are all binary operations and negation is a unary operation. Therefore, the non-terminal nodes of the corresponding expression trees have either one or two non-empty subtrees. That is, expression trees are usually binary trees.

What can we do with an expression tree? Perhaps the simplest thing to do is to print the expression represented by the tree. Notice that an inorder traversal of the tree in Figure visits the nodes in the order

Except for the missing parentheses, this is precisely the order in which the symbols appear in Equation !

This suggests that an inorder traversal should be used to print the expression. Consider an inorder traversal which, when it encounters a terminal node simply prints it out; and when it encounters a non-terminal node, does the following:

1.

Print a left parenthesis; and then

2.

traverse the left subtree; and then

3.

print the root; and then

4.

traverse the right subtree; and then

5.

print a right parenthesis.

Applying this procedure to the tree given in Figure we get which, despite the redundant parentheses, represents exactly the same expression as

Equation

Back to Lectures

Lecture 18

 Why Binary Search Trees?

 Expression Trees - Tree Traversal

In-Order Traversal o o o

Pre-Order Traversal

Post-Order Traversal

What's so great about binary search trees? They allow us to search very quickly.

If we have a binary search tree with 1,000,000 items, we will have to make at most 20 decisions, because at each decision, we split our search in half. In other words, if our binary search tree contains n items, we have to make at most log

2 n decisions.

Expression Trees

Expression trees are useful as a vehicle for discussing the traversal of a tree.

An expression tree is a binary tree which is used to represent a mathematical expression.

For example, if we have the expression (2 * (4 + (5 + 3))), we could construct a tree to represent it.

In an expression tree, the parent nodes are the operators, and the children are the operands. To find the result of this expression, we need to first solve (5 + 3), which is 8,

then solve (4 + 8), which is 12, and then finally solve 2 * 12, which is 24. So our root node will contain the operator within the outermost set of parentheses, its left child will be the value "2", and the right child will be the remaining expression that needs to be solved, which would be (4 + (5 + 3)).

When we talked about solving expressions using stacks, we had three different ways we could represent an expression:

 Infix, where the operator comes between its two operands

 Prefix, where the operator comes before its two operands

 Postfix, where the operator comes after its two operands

Given an expression tree, we can generate any of the three representations using one of the three traversals of a binary tree: in-order, pre-order, and post-order.

In-Order Traversal

In an infix expression, the operator comes between its operands, so if we want to generate the infix expression from an expression tree, we will need to print the operand on the left before we print out the operator. But what if the left operand is another expression to evaluate? We use recursion. We print out the entire left subtree, then print the current node, then print out the entire right subtree. void inOrder(BinaryTreeNode root)

{ if (null == root) return;

} inOrder(root.left()); // print the entire left subtree

System.out.println(root.data()); inOrder(root.right()); // print the entire right subtree return;

In this code, "root" refers to the root of the current subtree, not the root of the whole tree

(although we would have to start at the root of the whole tree). So, how does this work?

Let's look at the steps that this method takes for the simple expression 5 + 3 (for convenience, the nodes have been numbered):

 We start by calling inOrder(1) (the root).

 (1) is not null, so we call inOrder(2) (1's left).

 (2) is not null, so we call inOrder(2's left).

(2)'s left is null, so it just returns back to (2).

 We've done the left, so now we print the data at (2), which in this case is "5".

We've printed (2), so now we call inOrder(2's right).

(2)'s right is null, so it just returns back to (2).

 (2) has now finished, so it returns back to (1).

We've printed (1)'s left, so now we print the data at (1), which in this case is "+".

We've printed (1), so now we call inOrder(3).

 (3) is not null, so we call inOrder(3's left).

(3)'s left is null, so it just returns back to (3).

 We've done the left, so now we print the data at (3), which in this case is "3".

We've printed (3), so now we call inOrder(3's right).

 (3)'s right is null, so it just returns back to (3).

(3) has now finished, so it returns back to (1).

 (1) has now finished, so the result is "5+3", which is the infix representation of the tree.

Pre-Order Traversal

We can generate a prefix expression using a pre-order traversal. Much like for the inorder traversal, we will use recursion to print the entire left subtree and the entire right subtree. In a prefix expression, the operator comes before its two operands, so we will

have to print out the parent node's data before recursively printing its left and right children. void preOrder(BinaryTreeNode root)

{ if (null == root) return;

System.out.println(root.data()); preOrder(root.left()); // print the entire left subtree preOrder(root.right()); // print the entire right subtree return;

}

So instead of printing the data after we have printed the left subtree, we are going to print the data first, so that the operator will print out before its operands, giving us the prefix representation of the expression.

Post-Order Traversal

By now, you should see a pattern. The last representation is postfix, and we will use a post-order traversal to obtain it. Since in postfix the operator comes after its two operands, will recursively print the left and right subtrees before we print out the data at the current node. void postOrder(BinaryTreeNode root)

{ if (null == root) return; postOrder(root.left()); // print the entire left subtree postOrder(root.right()); // print the entire right subtree

System.out.println(root.data()); return;

}

Practice these traversals. Test your results on these trees:

Download