<<

Notes with the dark blue background CSCI 333 were prepared by the textbook author Data Structures

Clifford A. Shaffer Chapter 5 Department of 18, 20, 23, and 25 September 2002 Virginia Tech Copyright © 2000, 2001

Binary Trees Binary Example

A binary tree is made up of a finite of Notation: Node, nodes that is either empty or consists of a children, edge, node called the root together with two parent, ancestor, binary trees, called the left and right descendant, path, subtrees, which are disjoint from each depth, height, level, other and from the root. leaf node, internal node, subtree.

Full and Complete Binary Trees Full Binary Tree Theorem (1)

Full binary tree: Each node is either a leaf or Theorem: The number of leaves in a non-empty internal node with exactly two non-empty children. full binary tree is one more than the number of internal nodes. Complete binary tree: If the height of the tree is d, then all leaves except possibly level d are Proof (by Mathematical Induction): completely full. The bottom level has all nodes to the left side. Base case: A full binary tree with 1 internal node must have two leaf nodes. Induction Hypothesis: Assume any full binary tree T containing n-1 internal nodes has n leaves.

1 Full Binary Tree Theorem (2) Full Binary Tree Corollary

Induction Step: Given tree T with n internal Theorem: The number of null pointers in a nodes, pick internal node I with two leaf children. non-empty tree is one more than the Remove I’s children, call resulting tree T’. number of nodes in the tree. By induction hypothesis, T’ is a full binary tree with n leaves. Proof: Replace all null pointers with a Restore I’s two children. The number of internal pointer to an empty leaf node. This is a nodes has now gone up by 1 to reach n. The full binary tree. number of leaves has also gone up by 1.

Binary Tree Node Class (1) Binary Tree Node Class (2)

// Binary tree node class Elem& val() { return it; } template void setVal(const Elem& e) { it = e; } class BinNodePtr : public BinNode { inline BinNode* left() const private: { return lc; } Elem it; // The node's value void setLeft(BinNode* b) BinNodePtr* lc; // Pointer to left child { lc = (BinNodePtr*)b; } BinNodePtr* rc; // Pointer to right child inline BinNode* right() const public: { return rc; } BinNodePtr() { lc = rc = NULL; } void setRight(BinNode* b) BinNodePtr(Elem e, BinNodePtr* l =NULL, { rc = (BinNodePtr*)b; } BinNodePtr* r =NULL) bool isLeaf() { it = e; lc = l; rc = r; } { return (lc == NULL) && (rc == NULL); } };

Traversals (1) Traversals (2)

Any process for visiting the nodes in • Preorder traversal: Visit each node before some order is called a traversal. visiting its children. • Postorder traversal: Visit each node after Any traversal that lists every node in visiting its children. the tree exactly once is called an • Inorder traversal: Visit the left subtree, enumeration of the tree’s nodes. then the node, then the right subtree.

2 Traversals (3) Traversal Example template // Good implementation // Return the number of nodes in the tree void preorder(BinNode* subroot) { template if (subroot == NULL) return; // Empty int count(BinNode* subroot) { visit(subroot); // Perform some action if (subroot == NULL) preorder(subroot->left()); return 0; // Nothing to count preorder(subroot->right()); return 1 + count(subroot->left()) } + count(subroot->right()); } template // Bad implementation void preorder2(BinNode* subroot) { visit(subroot); // Perform some action if (subroot->left() != NULL) preorder2(subroot->left()); if (subroot->right() != NULL) preorder2(subroot->right()); }

Binary Tree Implementation Binary Tree Implementation (1)

• Binary tree abstract class and implementation • Preorder traversal

Binary Tree Implementation (2) Union Implementation (1)

enum Nodetype {leaf, internal}; class VarBinNode { // Generic node class public: Nodetype mytype; // Store type for node union { struct { // Internal node VarBinNode* left; // Left child VarBinNode* right; // Right child Operator opx; // Value } intl; Operand var; // Leaf: Value only };

This implementation uses more space than you may think

3 Union Implementation (2) Union Implementation (3)

// Leaf constructor // Preorder traversal VarBinNode(const Operand& val) void traverse(VarBinNode* subroot) { { mytype = leaf; var = val; } if (subroot == NULL) return; // Internal node constructor if (subroot->isLeaf()) VarBinNode(const Operator& op, cout << "Leaf: “ VarBinNode* l, VarBinNode* r) { << subroot->var << "\n"; mytype = internal; intl.opx = op; else { intl.left = l; intl.right = r; cout << "Internal: “ } << subroot->intl.opx << "\n"; bool isLeaf() { return mytype == leaf; } traverse(subroot->leftchild()); VarBinNode* leftchild() traverse(subroot->rightchild()); { return intl.left; } } VarBinNode* rightchild() } { return intl.right; } };

Inheritance (1) Inheritance (2) class VarBinNode { // Abstract base class public: // Internal node virtual bool isLeaf() = 0; class IntlNode : public VarBinNode { }; private: VarBinNode* left; // Left child class LeafNode : public VarBinNode { // Leaf VarBinNode* right; // Right child private: Operator opx; // Operator value Operand var; // Operand value public: public: IntlNode(const Operator& op, LeafNode(const Operand& val) VarBinNode* l, VarBinNode* r) { var = val; } // Constructor { opx = op; left = l; right = r; } bool isLeaf() { return true; } bool isLeaf() { return false; } Operand value() { return var; } VarBinNode* leftchild() { return left; } }; VarBinNode* rightchild() { return right; } Operator value() { return opx; } }; Space for a pointer to the virtual function must be allocated in the base class

Inheritance (3) Composite (1)

class VarBinNode { // Abstract base class // Preorder traversal public: void traverse(VarBinNode *subroot) { virtual bool isLeaf() = 0; if (subroot == NULL) return; // Empty virtual void trav() = 0; if (subroot->isLeaf()) // Do leaf node }; cout << "Leaf: " << ((LeafNode *)subroot)->value() class LeafNode : public VarBinNode { // Leaf << endl; private: else { // Do internal node Operand var; // Operand value cout << "Internal: " public: << ((IntlNode *)subroot)->value() LeafNode(const Operand& val) << endl; { var = val; } // Constructor traverse(((IntlNode *)subroot)-> bool isLeaf() { return true; } leftchild()); Operand value() { return var; } traverse(((IntlNode *)subroot)-> void trav() { cout << "Leaf: " << value() rightchild()); << endl; } } }; } Notice the casting!

4 Composite (2) Composite (3) class IntlNode : public VarBinNode { // Preorder traversal private: void traverse(VarBinNode *root) { VarBinNode* lc; // Left child if (root != NULL) VarBinNode* rc; // Right child root->trav(); Operator opx; // Operator value } public: IntlNode(const Operator& op, VarBinNode* l, VarBinNode* r) { opx = op; lc = l; rc = r; } Now you can traverse the tree without casting bool isLeaf() { return false; } VarBinNode* left() { return lc; } VarBinNode* right() { return rc; } Operator value() { return opx; } void trav() { cout << "Internal: " << value() << endl; if (left() != NULL) left()->trav(); if (right() != NULL) right()->trav(); } };

Space Overhead (1) Space Overhead (2)

From the Full Binary Tree Theorem: Eliminate pointers from the leaf nodes: • Half of the pointers are null. n/2(2p) p = If leaves store only data, then overhead n/2(2p) + dn p + d depends on whether the tree is full. This is 1/2 if p = d. Ex: All nodes the same, with two pointers to 2p/(2p + d) if data only at leaves ⇒ 2/3 children: overhead. • Total space required is (2p + d)n • Overhead: 2pn Note that some method is needed to • If p = d, this means 2p/(2p + d) = 2/3 overhead. distinguish leaves from internal nodes.

Array Implementation (1) Binary Tree Implementation

• Implementation with union (Fig 5.9) • Implementation with inheritance (Fig 5.10) • Implementation with inheritance (Fig 5.11) Position 0 1 2 3 4 5 6 7 8 9 10 11 Parent -- 0 0 1 1 2 2 3 3 4 4 5 Left Child 1 3 5 7 9 11 ------Right Child 2 4 6 8 10 ------Left Sibling -- -- 1 -- 3 -- 5 -- 7 -- 9 -- Right Sibling -- 2 -- 4 -- 6 -- 8 -- 10 -- --

5 Array Implementation (1) Binary Search Trees

Parent (r) = BST Property: All elements stored in the left subtree of a node with value K have values < K. Leftchild(r) = All elements stored in the right subtree of a node with value K have values >= K. Rightchild(r) = Leftsibling(r) =

Rightsibling(r) =

BST ADT(1) BST ADT(2)

// BST implementation for the Dictionary ADT public: template ~BST() { clearhelp(root); } class BST : public Dictionary { nodecount = 0; } private: bool insert(const Elem& e) { BinNode* root; // Root of the BST root = inserthelp(root, e); int nodecount; // Number of nodes nodecount++; void clearhelp(BinNode*); return true; } BinNode* bool remove(const Key& K, Elem& e) { inserthelp(BinNode*, const Elem&); BinNode* t = NULL; BinNode* root = removehelp(root, K, t); deletemin(BinNode*,BinNode*&); if (t == NULL) return false; BinNode* removehelp(BinNode*, e = t->val(); const Key&, BinNode*&); nodecount--; bool findhelp(BinNode*, const Key&, delete t; Elem&) const; return true; } void printhelp(BinNode*, int) const;

BST ADT(3) BST Search

bool removeAny(Elem& e) { // Delete min value template BinNode* t; bool BST:: root = deletemin(root, t); e = t->val(); findhelp(BinNode* subroot, delete t; const Key& K, Elem& e) const { nodecount--; if (subroot == NULL) return false; return true; else if (KEComp::lt(K, subroot->val())) } return findhelp(subroot->left(), K, e); bool find(const Key& K, Elem& e) const else if (KEComp::gt(K, subroot->val())) { return findhelp(root, K, e); } return findhelp(subroot->right(), K, e); int size() { return nodecount; } void print() const { else { e = subroot->val(); return true; } if (root == NULL) } cout << "The BST is empty.\n"; else printhelp(root, 0); }

6 BST Insert Remove Minimum Value

template BinNode* BST:: deletemin(BinNode* subroot, BinNode*& min) { if (subroot->left() == NULL) { min = subroot; return subroot->right(); } else { // Continue left subroot->setLeft( deletemin(subroot->left(), min)); return subroot; } }

BST Remove Cost of BST Operations

Find:

Insert:

Delete:

If the tree is not well-balanced, the cost can be very high.

Heaps What’s a BST for? : Complete binary tree with the heap • Performance in unreliable property: – Need a way to maintain balance • Min-heap: All values less than child values. • Max-heap: All values greater than child values. • Branching factor of two is much too small – Branches in the 100’s would be better The values are partially ordered. • Awful if data is almost sorted Heap representation: Normally the array- – Though you could rebalance based complete binary tree representation.

7 Good use for a heap Binary

• While nodes remain • BST class (Fig 5.14) – Select and remove the smallest value • BST class test – Update heap based on this selection

Huffman Tree Construction (1) Huffman Tree Construction (2)

Assigning Codes Coding and Decoding

Letter Freq Code Bits A set of codes is said to meet the prefix C 32 property if no code in the set is the prefix of another. D 42 E 120 F 24 Code for DEED: K 7 L 42 Decode 1011001110111101: U 37 Z 2 Expected cost per letter:

8