trees Trees also support a variable number of elements, but can provide faster search than linked lists. Example #1: Binary Tree A binary tree has one information(aka element) area and two pointers: left for nodes having a key value less than the node's key and right for nodes having a key value greater than the node's key. Trees have a root node (i.e., starting node), intermediate nodes, and leaf nodes (have no children). The depth of a tree is the length of its longest path. The depth for the binary in example #1 is 3. For example #2, what is the depth? A tree is considered balanced if each node is balanced: the absolute value of the difference in depth of its left subtree and depth of its right subtree is <= 1. Example #2: another binary tree If a tree is balanced, how many comparisons does it take to reach a particular value? What is the worst case? which nodes are balanced? Worst Case Unbalanced Tree The worst case of being unbalanced is when none of the nodes has more than 1 child and the depth is greater than 2. Example #3 Binary Tree Typedefs For pointers, we define a left pointer and a right pointer. malloc() is used to allocate a node. typedef struct NodeT { Element element; struct NodeT *pLeft; struct NodeT *pRight; } NodeT; typedef struct { NodeT *pRoot; } TreeImp; typedef TreeImp *Tree; Searching for a value - iterative algorithm Assume match contains the value to find. Search simply follows the pointers. If the match < node's value, follow to the left. If the match > node's value, follow to the right. If we hit NULL, there isn't a match. Searching for a value - recursive algorithm searchT() should return a pointer to the node containing matchKey or NULL if not found. What are the termination and special cases? Not found (hit NULL) Matched Count the nodes in a tree Show the function countT(NodeT *p) which returns a count of the nodes in the tree. // iterative algorithm p = tree->pRoot; pFound = NULL; while (p != NULL) { if (matchKey == p->element.key) { pFound = p; break; } else if (matchKey < p->element.key) p = p->pLeft; else p = p->pRight; } // at this point, if pFound is NULL, matchKey wasn't found // recursive searchT function NodeT *searchT(NodeT *p, Key matchKey) { ?? } int countT(NodeT *p) { ?? } What are the termination / special cases? Tree traversals How can we traverse the entire tree touching every node? The order with which you "visit" (or process) a tree node may be important to certain algorithms. Three orders: preorder - node itself, left, right in order - left, node itself, right post order - left, right, node itself // Simple in order algorithm for printing the contents void printInOrder(NodeT *p) { if (p == NULL) return; printInOrder (p->pLeft); printf("%d\n", p->element.key); // visit printInOrder (p->pRight); } // let's practice tracing the algorithm (show trace on the board) With in order, what order are the values printed? Exercise: Show a recursive printPreOrder(NodeT *p) With in order, the values are visited in order. void printPreOrder(NodeT *p) { ?? } Exercise: Show a recursive function, prettyPrint, which shows children indented from the parent. This helps show the tree's shape. For the first example, we would show: 50 What side is printed first? 45 40 30 20 10 After inserting 25: 50 45 40 30 25 20 10 After inserting 35: 50 45 40 35 30 25 20 10 After inserting 70: 70 50 45 40 35 30 25 20 10 Our initial call: prettyPrintT(tree->pRoot, 0); void prettyPrintT(NodeT *p, int iIndent) { int i; if (p == NULL) return; ?? } Exercise: Show a recursive function sumTree() which sums the element.key values. int sumTree(NodeT *p) { ?? } Recursive Insertion Using By Address Parameter Passing As we did with linked lists, insertion with recursion can take advantage of by address parameter passing. Case 1: Empty List Initially, we pass &(tree->pRoot). Case 1: Empty Tree pp's value is the address of pRoot. *pp is NULL Case 2: Insert on a null left branch Initially: pp's value is the address of pRoot *pp is the address of the node containing 30 If we traverse to the left, we want pp to point to the address of the root's pLeft. At that point: pp's value is the address of pRoot->pLeft *pp is NULL Case 2: Insert 20 on left branch pp initially points to pRoot pp points to the root's pLeft Recursive *insertT (NodeT **pp, Element value) Notice that we didn't include pRoot as an additional parameter since we are planning to pass the address of pRoot to insertT and modify the value at the address if necessary. Our call: Element value = …; NodeT *pNew; Tree tree = newTree(); pNew = insertT(&(tree->pRoot), value); allocateNodeT This function allocates a binary tree node, assigns the element value,, and initializes the pointers to NULL. It returns a pointer to the newly allocated node. NodeT *insertT(NodeT **pp, Element value) { // If *pp is null, this is where we want to insert. if (*pp == NULL) { ?? } // does it match if (value.key == (*pp)->element.key) return *pp; // which side should we follow if (value.key < (*pp)->element.key) return insertT(??, value); else return insertT(??, value); } NodeT *allocateNodeT(Element value) { NodeT *pNew = (NodeT *) malloc(sizeof(NodeT)); pNew->element = value; pNew-pLeft = NULL; pNew->pRight = NULL; return pNew; }