An Introduction to Trees Alice E. Fischer Spring 2017 Alice E. Fischer An Introduction to Trees... 1/34 Spring 2017 1 / 34
Outline 1 Trees the Abstraction Definitions 2 Expression Trees 3 Binary Search Trees 4 Recursion and Recursive Thinking. C++ Implementation of a Binary Search Tree Alice E. Fischer An Introduction to Trees... 2/34 Spring 2017 2 / 34
Trees the Abstraction Definitions Definitions In mathematics, a tree is an acyclic undirected graph.... and a rooted tree is a tree where one node is called the root. In computing, a tree is an abstract data type that has nodes and edges that connect nodes. Each node (except the root) has exactly one predecessor. Each node is either a leaf, or it has 1 to n successors. For binary trees, n=2. Alice E. Fischer An Introduction to Trees... 3/34 Spring 2017 3 / 34
Trees the Abstraction Definitions Examples a. A typical tree with 9 nodes. b. A sparse tree with 9 nodes.. c. A full tree with 9 nodes. d. A complete tree. Alice E. Fischer An Introduction to Trees... 4/34 Spring 2017 4 / 34
Trees the Abstraction Definitions Properties of Trees Size of a tree: the number of nodes in the tree. Height of a tree: the length of the path from the root to the most distant leaf. Depth of a node N: the length of the path from N to the root. A tree can be sparse, bushy, full, or complete. A tree can be balanced or unbalanced. A tree can be represented in more than one way. Alice E. Fischer An Introduction to Trees... 5/34 Spring 2017 5 / 34
Trees the Abstraction Definitions Tree Height Tree height = 3 Tree height = 4 Depth of orange node = 2 Height of orange node =1 Depth of blue node = 4 Height of blue node = 0 Alice E. Fischer An Introduction to Trees... 6/34 Spring 2017 6 / 34
Trees the Abstraction Definitions Full and Complete A full tree with 9 nodes. A complete tree. Alice E. Fischer An Introduction to Trees... 7/34 Spring 2017 7 / 34
Trees the Abstraction Definitions Degenerate Trees An unbalanced tree. A degenerate tree. Alice E. Fischer An Introduction to Trees... 8/34 Spring 2017 8 / 34
Trees the Abstraction Definitions TreeNodes Left: A simple binary tree uses nodes with three members: a data value and two links. 17 14 0 0 29 0 26 A Leaf Node 17 'a' An Interior Node 17 \0 0 0 Right: A Huffman Code tree has two data fields and two pointers. Alice E. Fischer An Introduction to Trees... 9/34 Spring 2017 9 / 34
Trees the Abstraction Definitions Tree Applications Trees provide an efficient way to index large amounts of data. A tree is the natural representation for arithmetic expressions. A heap (a specialized tree) is the best implementation of a priority queue. In a search tree, the nodes data members are used to index the data. Balanced trees make searching faster. A trie is great for a spelling-checker or for data compression. Alice E. Fischer An Introduction to Trees... 10/34 Spring 2017 10 / 34
Expression Trees Expression Trees A compiler analyzes the expressions in a program and builds an expression tree for each one. In C++, we use infix notation and apply precedence and associativity to determine the meaning. Parentheses can be used to override the default order of evaluation. a + b * c - d a / (b + c) - d x = a - (b * b) - 2*(a + c) Alice E. Fischer An Introduction to Trees... 11/34 Spring 2017 11 / 34
Expression Trees Parse Trees A parse tree is built when a compiler translates an arithmetic expression. Parentheses, precedence and associativity are used to determine the shape of the tree. The tree is used by the optimizer and by the code generator a + b * c - d -- a / (b + c) - d -- + d / d a * a + b c b c Alice E. Fischer An Introduction to Trees... 12/34 Spring 2017 12 / 34
Expression Trees Prefix and Postfix Notation Some languages do not use infix notation for expressions. The same expression can be written without parentheses in either prefix or postfix form. Infix: the operator is written between its two operands. Prefix: the operator is written before its operand(s). Postfix: the operator is written after its operand(s). All three ways of writing the expression map into the same parse tree. Infix: a + b * c - d a / (b + c) - d Prefix: - + a * b c d - / a + b c d Postfix: a b c * + d - a b c + / d - Alice E. Fischer An Introduction to Trees... 13/34 Spring 2017 13 / 34
Expression Trees Traversal Order Prefix: void Tree::traverse( Node* t ){ if (t == nullptr) return; t->getdata().print( ); traverse( t->left ); traverse( t->right ); } Infix: void Tree::traverse( Node* t ){ if (t == nullptr) return; traverse( t->left ); t->getdata().print( ); traverse( t->right ); } Postfix: void Tree::traverse( Node* t ){ if (t == nullptr) return; traverse( t->left ); traverse( t->right ); t->getdata().print( ); } Alice E. Fischer An Introduction to Trees... 14/34 Spring 2017 14 / 34
Expression Trees Evaluation of any format gives the same answer. Evaluate an expression a=25, b=3, c=2, d=7. infix order: a / (b + c) - d == 25 / (3 + 2) - 7 == (25 / 5) - 7 = -2 Prefix order : - / a + b c d == - / 25 + 3 2 7 == - / 25 5 7 == - 5 7 == - 2 Postfix order: a b c + / d - == 25 3 2 + / 7 - == 25 5 / 7 - == 5 7 - == -2 Alice E. Fischer An Introduction to Trees... 15/34 Spring 2017 15 / 34
Expression Trees Evaluation of any format gives the same answer. Evaluate a different expression with a=25, b=3, c=2, d=7. Infix: a + b * c - d == 25 + 3 * 2-7 == 25 + 6-7 == 31-7 == 24 Prefix: - + a * b c d == - + 25 * 3 2 7 == - + 25 6 7 == - 31 7 == 24 Postfix: a b c * + d - == 25 3 2 * + 7 - == 25 6 + 7 - == 31 7 - == 24 Alice E. Fischer An Introduction to Trees... 16/34 Spring 2017 16 / 34
Expression Trees Use two Stacks to evaluate an infix expression We assume the operators are all binary operators, with two operands. To evaluate an expression such as x + 5-2 * z % 3 1 Initialize the operand stack to empty and the operator stack to an end-marker. 2 Read the next token. If it is an operand, put it on the operand stack. 3 If it is an operator, compare its precedence to the operator at the top of the operator-stack. See details on next slide. 4 Repeat steps (2, 3, and 4) until you come to the end of the expression. The value on the top of the operand stack is the answer. Alice E. Fischer An Introduction to Trees... 17/34 Spring 2017 17 / 34
Expression Trees Handling an operator If the new op has higher precedence than the old op, push it onto the operator-stack. Else pop the operand stack twice, into o-right and o-left, Do the operation at the top of the operation stack using the two operands, and push the result onto the operand stack. If you can t pop the operand stack twice, there is an error. Now repeat step 3, comparing the current operator to the new top of the operator stack. Alice E. Fischer An Introduction to Trees... 18/34 Spring 2017 18 / 34
Expression Trees Use one Stack to evaluate a postfix expression We assume the operators are all binary operators, with two operands. To evaluate an expression such as x 5 + 2 z * 3 % - 1 Put an end marker on the stack. 2 Read the next token. If it is an operand, put it on the stack. 3 If it is an operator, pop the stack twice, into op-right and op-left. If you can t pop the stack twice, there is an error. 4 Do the operation, push the answer back on the stack. 5 Repeat (2,3,4) until you come to the end of the expression. The value on the top of the stack is the answer. (Remove it) 6 If the stack-top is your end marker, the expression is correct. If it is anything else, the expression has an error. Alice E. Fischer An Introduction to Trees... 19/34 Spring 2017 19 / 34
Expression Trees Use a Queue to evaluate a prefix expression We assume the operators are all binary operators, with two operands. Here is a terribly slow but funny way to evaluate a prefix expression such as - + x 5 % * 2 z 3 WHILE the queue has more than one element DO Inspect the front element. If it is an operator followed by its two operands, evaluate it, append the resulting value to the rear of the queue AND remove operator and arguments from the front of the queue. If it is an operator not followed by all its arguments, remove it from the front of the queue and copy it to the rear. Otherwise it is an operand. Remove it from the front and copy it to the rear. Alice E. Fischer An Introduction to Trees... 20/34 Spring 2017 20 / 34
Binary Search Trees Binary Search Trees Definition Recursive print and destructor Search Insertion Deletion C++ implementation Alice E. Fischer An Introduction to Trees... 21/34 Spring 2017 21 / 34
Binary Search Trees Definition: Binary Search Tree A Binary Search Tree is a binary tree with the following properties; The root may be NULL or it may point at a node. The first node inserted becomes the root of the tree and remains root until deleted. Every node has zero, one, or two successors. Each tree node has a value. That value, or part of it, is the key field. Given any node, n, all values on its left subtree are less than the value of n, and all values on its right subtree are greater than the value of n. We will work with a version in which duplicate key values are not allowed. Alice E. Fischer An Introduction to Trees... 22/34 Spring 2017 22 / 34
Binary Search Trees Definition: Binary Search Tree root 17 29 0 14 0 0 26 0 0 Alice E. Fischer An Introduction to Trees... 23/34 Spring 2017 23 / 34
Binary Search Trees Example: Building a Binary Search Tree We insert the numbers 17, 29, 14, and 26 into an empty tree. All nodes are diagrammed in the simplified form. root 17 root 17 29 root 17 root 17 14 29 14 29 26 Alice E. Fischer An Introduction to Trees... 24/34 Spring 2017 24 / 34
Binary Search Trees Example: Building a Binary Search Tree We continue by inserting 5, 16, 15, 10, 20, and 25. root 17 root 14 29 17 26 14 29 5 16 26 root 17 10 15 20 14 29 25 5 16 26 Alice E. Fischer An Introduction to Trees... 25/34 Spring 2017 25 / 34
Recursion and Recursive Thinking. Recursion Recursion can replace loops. Some languages do not even support loops. Loops take less time and space. Some but not all recursions (tail recursions) can be replaced by loops. Alice E. Fischer An Introduction to Trees... 26/34 Spring 2017 26 / 34
Recursion and Recursive Thinking. Recursion Looping but Different A loop must have: a definite end-test you must make progress on every iteration A recursion must have: a definite end-test you must make progress on every recursive call (simplify the problem) The differences? Looping uses a loop variable to track the progress and end the loop. Recursion makes a new function call, with a new stack frame at every recursive step, and deallocates the stack frames upon return. Alice E. Fischer An Introduction to Trees... 27/34 Spring 2017 27 / 34
Recursion and Recursive Thinking. Recursive Thinking Iterative Thinking: add up the numbers in a list. initialize the sum start at the beginning of the list and process ( add in)m one element at a time until you reach the end. Go to the next list element after each processing step. Recursive Thinking Remember the first element on the list. Add up the rest Then add the result to the first element. Alice E. Fischer An Introduction to Trees... 28/34 Spring 2017 28 / 34
Recursion and Recursive Thinking. Array Traversal We normally use a loop to traverse (visit every element of) an array. ar[0] ar[1] ar[2] ar[3] ar[4] ar[5] ar[6] ar[7] ar[8] dust scum prize socks dishes filth dirt sink tub Sequential search of an array: scan = 0; while ( true ){ get the contents of the ar[scan]. if contents equals "prize", break ++scan; } Alice E. Fischer An Introduction to Trees... 29/34 Spring 2017 29 / 34
Recursion and Recursive Thinking. Recursive Thinking Iterative Thinking: Sequential Search Start at the beginning of the list and test one element at a time. Leave the loop if you find the wanted element or if you reach the end. Otherwise, go on to the next item in the list.. Recursive Thinking: Sequential Search Return if the list is null. Return if the current thing on the list is the wanted element. Otherwise, search the rest of the list recursively, starting with the next element Alice E. Fischer An Introduction to Trees... 30/34 Spring 2017 30 / 34
Recursion and Recursive Thinking. Recursive Thinking We normally use recursion to traverse (visit every element of) a tree. This is a binary search tree. The nodes were inserted in the order shown at the top. dust scum prize socks dishes filth dirt sink tub root dust dishes scum dirt filth prize socks sink tub Alice E. Fischer An Introduction to Trees... 31/34 Spring 2017 31 / 34
Recursion and Recursive Thinking. Recursive Thinking: A Tree Search Call the search function with the root of the tree as the parameter. Return if the parameter is null. Return the value in the current node if it is the wanted element. Otherwise, search the left side of the tree recursively. Then search the right side of the tree recursively. Alice E. Fischer An Introduction to Trees... 32/34 Spring 2017 32 / 34
Recursion and Recursive Thinking. Search a Tree We normally use recursion to traverse (visit every element of) a tree. find( current, goal ){ result = null; if current == null } then return null. if current s contents equals goal, return current. else result = find (current.leftson, goal) } if result!= null return result. else return find(current.rightson, goal) Alice E. Fischer An Introduction to Trees... 33/34 Spring 2017 33 / 34
Recursion and Recursive Thinking. C++ Implementation of a Binary Search Tree C++ Implementation Overview The Tree class has two private helper classes inside it. class Node stores the data and links to other nodes. struct Finger is used when searching the tree, and to position pointers for insertion and deletion. Tree has a public interface that supports construction, deletion, printing, insertion, deletion, and searching. Several of the functions in the public interface are implemented by private recursive functions. The rest of the public functions use a loop, not recursion, because they follow a linear access path down the tree. If written recursively, these functions would be tail recursions. Alice E. Fischer An Introduction to Trees... 34/34 Spring 2017 34 / 34