1 Trees & More Tables (Walls & Mirrors - Chapter 10 & Beginning of 11)
2 Overview Terminology Binary Trees Pointer-Based Representation of a Binary Tree Array-Based Representation of a Binary Tree Traversing a Binary Tree Binary Search Tree Implementation of a Table Treesort
3 Terminology A tree is a collection of nodes and directed edges, satisfying the following properties: –There is one specially designated node called the root, which has no edges pointing to it. –Every node except the root has exactly one edge pointing to it. –There is a unique path (of nodes and edges) from the root to each node.
4 Graphical Representation Trees, as defined on the preceding slide, are typically drawn with circles (or rectangles) representing the nodes and arrows representing the edges. The root is typically placed at the top of the diagram, with the rest of the tree below it. Trees:Not Trees: root edge node
5 Terminology (Cont’d.) If an edge goes from node a to node b, then a is called the parent of b, and b is called a child of a. Children of the same parent are called siblings. If there is a path from a to b, then a is called an ancestor of b, and b is called a descendent of a. A node with all of its descendants is called a subtree. If a node has no children, then it is called a leaf of the tree. If a node has no parent (there will be exactly one of these), then it is the root of the tree.
6 Terminology: Example A BC DE F GH I JK A is the root D, E, G, H, J & K are leaves B is the parent of D, E & F D, E & F are siblings and children of B I, J & K are descendants of B A & B are ancestors of I subtree
7 Binary Trees Intuitively, a binary tree is LIKE a tree in which each node has no more than two children. Formally, a binary tree is a set T of nodes such that either: – T is empty, or –T consists of a single node, r, called the root, and two (non- overlapping) binary trees, called the left and right subtrees of r. UNLIKE trees, binary trees may be empty, and they distinguish between left and right subtrees. (These two binary trees are distinct.)
8 Binary Search Trees A binary search tree is a binary tree in which each node, n, has a value satisfying the following properties: –n’s value is > all values in its left subtree, T L, –n’s value is < all values in its right subtree, T R, and –T L and T R are both binary search trees. John Peter Brenda AmyMaryTom
9 Terminology (Cont’d.) Intuitively, the level of a node is the number of nodes on a path from the root to the node. Formally, level of node, n: – If n is the root of a tree, then it is at level 1. – Otherwise, its level is 1 greater than the level of its parent. Height of binary tree, T: – If T is empty, its height is 0. – Otherwise, its height is the maximum level of its nodes, or, equivalently, 1 greater than the height of the root’s taller subtree. Namely, height(T) = 1 + max { height( T L ), height(T R ) }
10 Terminology (Cont’d.) Intuitively, a binary tree is full if it has no missing nodes. Formally, a binary tree of height h is full if – It is empty (h = 0). – Otherwise, the root’s subtrees are full binary trees of height h – 1. If not empty, each node has 2 children, except the nodes at level h which have no children. Alternatively, the binary tree has all of its leaves at level h.
11 Terminology (Cont’d.) Intuitively, a binary tree of height h is complete if it is full down to level h – 1, and level h is filled from left to right. Formally, a binary tree of height h is complete if – All nodes at level h – 2 and above have 2 children each, – If a node at level h – 1 has children, all nodes to its left at the same level have 2 children each, and – If a node at level h – 1 has 1 child, it is a left child.
12 Terminology (Cont’d.) A binary tree is balanced if the difference in height between any node’s left and right subtree is 1. Note that: –A full binary tree is also complete. –A complete binary tree is not always full. –Full and complete binary trees are also balanced. –Balanced binary trees are not always full or complete.
13 Binary Tree: Pointer-Based Representation struct TreeNode;// Binary Tree nodes are struct’s typedef string TreeItemType;// items in TreeNodes are string’s class BinaryTree { public: // declarations of public member functions private: TreeNode *root;// pointer to root of Binary Tree }; struct TreeNode// node in a Binary Tree: {// place in Implementation file TreeItemType item; TreeNode *leftChild;// pointer to TreeNode’s left child TreeNode *rightChild;// pointer to TreeNode’s right child };
14 Binary Tree: Array-Based Representation Basic Idea: Instead of using pointers to the left and right child of a node, use indices into the array of nodes representing the binary tree. Also, use variable free as an index to the first position in the array that is available for a new entry. Use either the left or right child indices to indicate additional, available positions. Together, the list of available positions in the array is called the free list.
15 Binary Tree: Array-Based Representation root 0 free 6 Jane Alan TomBob NancyEllen
16 Binary Tree: Array-Based Representation root 0 free 7 * Mary Added under Nancy. Jane Alan TomBob NancyEllen Mary
17 Binary Tree: Array-Based Representation root 0 free 4 * Ellen deleted. Jane Alan TomBob Nancy Mary
18 Binary Tree: Array-Based Representation const int MaxNodes = 100;// maximum size of a Binary Tree typedef string TreeItemType;// items in TreeNodes are string’s struct TreeNode// node in a Binary Tree { TreeItemType item; int leftChild;// index of TreeNode’s left child int rightChild;// index of TreeNode’s right child }; class BinaryTree { public: // declarations of public member functions private: TreeNode node[MaxNodes]; int root;// index of root of Binary Tree int free;// index of free list, linked by rightChild };
19 Traversing a Binary Tree Preorder Inorder Postorder
20 Preorder Traversal of a Binary Tree Basic Idea: 1) Visit the root. 2) Recursively invoke preorder on the left subtree. 3) Recursively invoke preorder on the right subtree.
21 Preorder Traversal of a Binary Tree Preorder Result: 60, 20, 10, 40, 30, 50,
22 Inorder Traversal of a Binary Tree Basic Idea: 1) Recursively invoke inorder on the left subtree. 2) Visit the root. 3) Recursively invoke inorder on the right subtree.
23 Inorder Traversal of a Binary Tree Inorder Result: 10, 20, 30, 40, 50, 60,
24 Postorder Traversal of a Binary Tree Basic Idea: 1) Recursively invoke postorder on the left subtree. 2) Recursively invoke postorder on the right subtree. 3) Visit the root.
25 Postorder Traversal of a Binary Tree Postorder Result: 10, 30, 50, 40, 20, 70,
26 Pointer-Based, Preorder Traversal in C++ // FunctionType is a pointer to a function with argument // (TreeItemType &) that returns void. typedef void (*FunctionType) (TreeItemType &treeItem); // Public member function void BinaryTree::preorderTraverse( FunctionType visit ) { preorder( root, visit ); }
27 Pointer-Based, Preorder Traversal in C++ // Private member function void BinaryTree::preorder( TreeNode *treePtr, FunctionType visit ) { if( treePtr != NULL ) { visit( treePtr -> item ); preorder( treePtr -> leftChild, visit ); preorder( treePtr -> rightChild, visit ); }
28 Pointer-Based, Preorder Traversal in C++ Suppose that we define the function void printItem( TreeItemType &treeItem ) { cout << treeItem << endl; } Then, // create myTree BinaryTree myTree; // load data into myTree... // print TreeItems encountered in preorder traversal of myTree myTree.preorderTraverse( &printItem );
29 Pointer-Based, Inorder & Postorder Traversal The functions for Inorder and Postorder traversal of a binary tree are very similar to the function for Preorder traversal, and are left as an exercise.
30 Nonrecursive Traversal of a Binary Tree Basic Idea for a Nonrecursive, Inorder Traversal: 1) Push a pointer to the root of the binary tree onto a stack. 2) Follow leftChild pointers, pushing each one onto the stack, until a NULL leftChild pointer is found. 3) Process (visit) the item in this node. 4) Get the node’s rightChild pointer: –If it is not NULL, then push it onto the stack, and return to step 2 with the leftChild pointer of this rightChild. –If it is NULL, then pop a node pointer from the stack, and return to step 3. If the stack is empty (so nothing could be popped), then stop — the traversal is done.
31 Nonrecursive Traversal of a Binary Tree The logic for a nonrecursive, preorder or postorder traversal is similar to what was presented on the previous slide for an inorder traversal. ~14 lines of code (see chapter 10 of Walls & Mirrors) plus the code for manipulating a stack are required to implement a nonrecursive traversal. Compare this with the 5 lines of code that were required for our recursive solutions. This example illustrates how recursion can sometimes be used to produce a clear and concise implementation.
32 Binary Search Tree Implementation of a Table Recall that the supported operations for an ADT Table include: –Create an empty table –Destroy a table –Determine whether a table is empty –Determine the number of items in a table –Insert a new item into a table –Delete the item with a given search key from a table –Retrieve the item with a given search key from a table –Traverse the items in a table in sorted, search-key order
33 Table: Binary Search Tree Implementation typedef string KeyType;// Table search-keys are strings struct dataItem// all data for an item is put into { KeyType key;// one struct for convenience // other data members are included here }; typedef dataItem TableItemType; // items in Table are dataItems // returns tableItem’s searchKey KeyType getKey( const TableItemType &tableItem ) { return( tableItem.key ); } // FunctionType is a pointer to a function with argument // (TableItemType &) that returns void, used by Table traverse( ) typedef void (*FunctionType) (TableItemType &tableItem);
34 Table: Binary Search Tree Implementation typedef TableItemType TreeItemType; // TableItems are stored struct TreeNode; // in TreeNodes class Table { public: // declarations of public member functions private: TreeNode *root;// root of Binary Search Tree int size;// number of items in Table }; struct TreeNode// node in a Binary Search Tree: {// place in Implementation file TreeItemType item; TreeNode *leftChild;// pointer to TreeNode’s left child TreeNode *rightChild;// pointer to TreeNode’s right child };
35 Table: Binary Search Tree Implementation An alternative (and perhaps, better) implementation of a Table as a Binary Search Tree would define struct dataItem { KeyType key; // other data members are included here }; and struct TreeNode { TreeItemType item; TreeNode *leftChild; TreeNode *rightChild; }; as Classes, with constructors to set the key for a dataItem and to initialize leftChild and rightChild to NULL. getKey( ) would become a public member function, providing access to key. (See chapter 10 of Walls & Mirrors for details.)
36 Table: Public Member Function Definitions // default constructor, which creates a new empty Table Table::Table( ) : root( NULL ), size( 0 ) { } // copy constructor, which copies origTable to a new Table Table::Table( const Table &origTable ) { copyTree( origTable.root, root ); size = origTable.size; } // destructor, which destroys a Table Table::~Table( ) { destroyTree( root ); }
37 Table: Private Member Function Definition // copies tree rooted at origTreePtr to a new tree rooted at newTreePtr void Table::copyTree( TreeNode *origTreePtr, TreeNode *&newTreePtr ) const { if( origTreePtr != NULL )// copy nodes using preorder traversal { newTreePtr = new TreeNode; assert( newTreePtr != NULL ); newTreePtr -> item = origTreePtr -> item; copyTree( origTreePtr -> leftChild, newTreePtr -> leftChild ); copyTree( origTreePtr -> rightChild, newTreePtr -> rightChild ); } else newTreePtr = NULL; }
38 Table: Private Member Function Definition // destroys tree rooted at treePtr void Table::destroyTree( TreeNode *&treePtr ) { if( treePtr != NULL ) { // delete nodes using postorder traversal destroyTree( treePtr -> leftChild ); destroyTree( treePtr -> rightChild ); delete treePtr; treePtr = NULL; }
39 Table: Public Member Function Definitions // returns true if the Table is empty, otherwise returns false bool Table::isEmpty( ) const { return size = = 0; } // returns the number of items in the Table int Table::length( ) const { return size; }
40 Table: Public Member Function Definitions // inserts newItem into a Table in sorted order bool Table::insert( TableItemType &newItem ) { if( treeInsertItem( root, newItem ) ) { size++; return true; } return false; } // removes item with the given searchKey from a Table bool Table::remove( KeyType searchKey ) { if( treeDeleteItem( root, searchKey ) ) { size – –; return true; } return false; }
41 Table: Public Member Function Definitions // retrieves a copy of item with the given searchKey from a Table bool Table::retrieve( KeyType searchKey, TableItemType &tableItem ) const { return treeRetrieveItem( root, searchKey, tableItem ); }
42 Table: Private Member Function Definition // inserts newItem into a Binary Search Tree rooted at treePtr bool Table::treeInsertItem( TreeNode *&treePtr, const TreeItemType &newItem ) { if( treePtr = = NULL ) { treePtr = new TreeNode; assert( treePtr != NULL ); treePtr -> item = newItem; treePtr -> leftChild = treePtr -> rightChild = NULL; return true; } if( getKey( newItem ) = = getKey( treePtr -> item ) ) return false; if( getKey( newItem ) item ) ) return treeInsertItem( treePtr -> leftChild, newItem ); else return treeInsertItem( treePtr -> rightChild, newItem ); }
43 Table: Private Member Function Definition // retrieves a copy of item with the given searchKey from a Binary // Search Tree rooted at treePtr bool Table::treeRetrieveItem( TreeNode *treePtr, KeyType searchKey, TreeItemType &treeItem ) const { if( treePtr = = NULL ) return false; KeyType itemKey = getKey( treePtr -> item ); if( searchKey = = itemKey ) { treeItem = treePtr -> item; return true; } if( searchKey < itemKey ) return treeRetrieveItem( treePtr -> leftChild, searchKey, treeItem ); else return treeRetrieveItem( treePtr -> rightChild, searchKey, treeItem ); }
44 Table: Private Member Function Definition // deletes item with the given searchKey from a Binary Search Tree // rooted at treePtr bool Table::treeDeleteItem( TreeNode *&treePtr, KeyType searchKey ) { if( treePtr = = NULL ) return false; KeyType itemKey = getKey( treePtr -> item ); if( searchKey = = itemKey ) { treeDeleteNode( treePtr ); return true; } if( searchKey < itemKey ) return treeDeleteItem( treePtr -> leftChild, searchKey ); else return treeDeleteItem( treePtr -> rightChild, searchKey ); }
45 Delete Node at treePtr from a Tree There are 4 cases: 1) treePtr points to a leaf 2) treePtr points to a node with no leftChild 3) treePtr points to a node with no rightChild 4) treePtr points to a node with both leftChild and rightChild
46 Table: Private Member Function Definition // delete node at treePtr from a tree void Table::treeDeleteNode( TreeNode *&treePtr ) { if( treePtr -> leftChild = = NULL && treePtr -> rightChild = = NULL ) { /* delete leaf pointed to by treePtr */ } else if( treePtr -> leftChild = = NULL ) { /* delete node at treePtr, making treePtr point to rightChild */ } else if( treePtr -> rightChild = = NULL ) { /* delete node at treePtr, making treePtr point to leftChild */ } else { /* copy item from leftmost descendant of rightChild */ /* to node at treePtr; delete leftmost descendant */ } }
47 Case 1: treePtr Points to a Leaf 1) Delete the leaf 2) Set treePtr to NULL treePtr
48 Case 1: treePtr Points to a Leaf /* delete leaf pointed to by treePtr */ delete treePtr; treePtr = NULL;
49 Case 2: treePtr Points to a Node with No leftChild 1) Save treePtr to delPtr 2) Set treePtr to treePtr -> rightChild 3) Delete node at delPtr treePtr delPtr 4060
50 Case 2: treePtr Points to a Node with No leftChild /* delete node at treePtr, making treePtr point to rightChild */ TreeNode *delPtr = treePtr; treePtr = treePtr -> rightChild; delPtr -> rightChild = NULL; delete delPtr;
51 Case 3: treePtr Points to a Node with No rightChild 1) Save treePtr to delPtr 2) Set treePtr to treePtr -> leftChild 3) Delete node at delPtr treePtr delPtr 3050
52 Case 3: treePtr Points to a Node with No rightChild /* delete node at treePtr, making treePtr point to leftChild */ TreeNode *delPtr = treePtr; treePtr = treePtr -> leftChild; delPtr -> leftChild = NULL; delete delPtr;
53 Case 4: treePtr Points to a Node with Both leftChild and rightChild 1) Let nodePtr be the TreeNode pointer that points to the leftmost descendant of treePtr -> rightChild 2) Save nodePtr -> item to treeItem 3) Save nodePtr to delPtr 4) Set nodePtr to nodePtr -> rightChild 5) Delete node at delPtr 6) Set treePtr -> item to treeItem treeItem treePtr 3010 nodePtr delPtr
54 Case 4: treePtr Points to a Node with Both leftChild and rightChild /* copy item from leftmost descendant of rightChild to node */ /* at treePtr; delete leftmost descendant */ TreeItemType leftmostItem; processLeftmost( treePtr -> rightChild, leftmostItem ); treePtr -> item = leftmostItem;
55 Table: Private Member Function Definition /* find leftmost descendant of the node pointed to by nodePtr; */ /* copy its item to treeItem, then delete it */ void Table::processLeftmost( TreeNode *&nodePtr, TreeItemType &treeItem ) { if( nodePtr -> leftChild != NULL ) processLeftmost( nodePtr -> leftChild, treeItem ); else { treeItem = nodePtr -> item; TreeNode *delPtr = nodePtr; nodePtr = nodePtr -> rightChild; delPtr -> rightChild = NULL; delete delPtr; }
56 Table: Public Member Function Definition // traverses a Table in sorted search-key order, calling // function visit( ) once for each item in the Table void Table::traverse( FunctionType visit ) { treeTraverse( root, visit ); }
57 Table: Private Member Function Definition // performs inorder traverse of a Binary Search Tree, calling // function visit( ) once at each node in the tree void Table::treeTraverse( TreeNode *treePtr, FunctionType visit ) { if( treePtr != NULL ) { treeTraverse( treePtr -> leftChild, visit ); visit( treePtr -> item ); treeTraverse( treePtr -> rightChild, visit ); }
58 Treesort: Basic Idea Problem: Sort an array of items into search-key order. 1) Insert items from the array into a Binary Search Tree (or Binary-Search-Tree-based Table). 2) Perform an inorder traversal of the Binary Search Tree, copying each item back into the array. When step 2 is done, the array will be in sorted order!
59 Treesort void treeSort( TableItemType a[ ], int n ) { Table table; // insert items from a[ ] into table for( int i = 0; i < n; i++ ) table.insert( a[i] ); // copy items from table into a[ ] in sorted, search-key order table.traverse( ©Item, a ); } void copyItem( TableItemType &tableItem, TableItemType a[ ] ) { static int k = 0; a[k++] = tableItem; }
60 Treesort (Cont’d.) typedef void (*FunctionType2) (TableItemType &tableItem, TableItemType a[ ] ); void Table::traverse( FunctionType2 visit, TableItemType a[ ] ) { treeTraverse( root, visit, a ); } void Table::treeTraverse( TreeNode *treePtr, FunctionType2 visit, TableItemType a[ ] ); { if( treePtr != NULL ) { treeTraverse( treePtr -> leftChild, visit, a ); visit( treePtr -> item, a ); treeTraverse( treePtr -> rightChild, visit, a ); }
61 Treesort: Efficiency Each insertion into a Binary Search Tree requires O( log n ) operations in the best and average cases and O( n ) operations in the worst case. So, performing n insertions requires O( n * log n ) operations in the best and average cases and O( n 2 ) operations in the worst case. Traversal of the Binary Search Tree requires O( n ) operations in all cases. Therefore, the overall Growth Rate of Treesort is O( n * log n ) operations in the best and average cases and O( n 2 ) operations in the worst case — the same as Quicksort. Binary-Search-Tree algorithms are efficient as long as the tree is balanced. If not balanced, Binary-Search-Tree algorithms can become as inefficient as algorithms on a linear List. We will learn about maintaining balanced trees in the coming chapters.