CSCI 333 Data Structures Binary Trees Binary Tree Example Full And
Total Page:16
File Type:pdf, Size:1020Kb
Notes with the dark blue background CSCI 333 were prepared by the textbook author Data Structures Clifford A. Shaffer Chapter 5 Department of Computer Science 18, 20, 23, and 25 September 2002 Virginia Tech Copyright © 2000, 2001 Binary Trees Binary Tree Example A binary tree is made up of a finite set 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 <class Elem> void setVal(const Elem& e) { it = e; } class BinNodePtr : public BinNode<Elem> { inline BinNode<Elem>* left() const private: { return lc; } Elem it; // The node's value void setLeft(BinNode<Elem>* b) BinNodePtr* lc; // Pointer to left child { lc = (BinNodePtr*)b; } BinNodePtr* rc; // Pointer to right child inline BinNode<Elem>* right() const public: { return rc; } BinNodePtr() { lc = rc = NULL; } void setRight(BinNode<Elem>* 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 <class Elem> // Good implementation // Return the number of nodes in the tree void preorder(BinNode<Elem>* subroot) { template <class Elem> if (subroot == NULL) return; // Empty int count(BinNode<Elem>* 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 <class Elem> // Bad implementation void preorder2(BinNode<Elem>* 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 <class Key, class Elem, BST() { root = NULL; nodecount = 0; } class KEComp, class EEComp> ~BST() { clearhelp(root); } class BST : public Dictionary<Key, Elem, void clear() { clearhelp(root); root = NULL; KEComp, EEComp> { nodecount = 0; } private: bool insert(const Elem& e) { BinNode<Elem>* root; // Root of the BST root = inserthelp(root, e); int nodecount; // Number of nodes nodecount++; void clearhelp(BinNode<Elem>*); return true; } BinNode<Elem>* bool remove(const Key& K, Elem& e) { inserthelp(BinNode<Elem>*, const Elem&); BinNode<Elem>* t = NULL; BinNode<Elem>* root = removehelp(root, K, t); deletemin(BinNode<Elem>*,BinNode<Elem>*&); if (t == NULL) return false; BinNode<Elem>* removehelp(BinNode<Elem>*, e = t->val(); const Key&, BinNode<Elem>*&); nodecount--; bool findhelp(BinNode<Elem>*, const Key&, delete t; Elem&) const; return true; } void printhelp(BinNode<Elem>*, int) const; BST ADT(3) BST Search bool removeAny(Elem& e) { // Delete