106731499 -1- Chapter 10 Trees Binary Tree Structure Designing TreeNode Functions Using Tree Scan Algorithms Binary search trees Using Binary search Trees The BinSTree Implementation 106731499 -2- Binary Tree Structure Arrays and Linked Lists are linear lists. A linear list is a general description for data structures that include arrays, stacks, queues, and linked lists. Linked Lists have the advantages of flexibility over arrays, however their weak feature is that they are sequential.. Searching a linked list for some target key requires a sequential search. O(n) QUESTION? Can the nodes of a linked list be rearranged so that we can search in time O ( log n ) ? IDEA: To keep the advantages of linked storage and obtain the speed of binary search. SOLUTION: Store the nodes in the structure of the comparison tree itself using links to describe the relations ( left and right ) of the tree. Such a tree is called a binary tree. 106731499 -3- Tree Terminology -A tree is a nonlinear structure, that consists of nodes and branches. The organization flows from the root to the outer nodes, called leaves. A B D C E F I G H J Tree Structure: - A collection of nodes that originates from the starting node called the root or parent. - The root or parent node may point to other nodes called its children. - The children of a node or children of these children are called descendants. - The parent and grandparents are called ancestors. - A node with no children is called a leaf node. - Each node in the tree is the root of its own subtree which is defined as a node and all descendants of the node. - You move from the parent node to other descendants along a path. - Each non-root node has a single parent - The path between the root and a node provides a measure called the level of a node. The level of a node is the length of the path from the root to the node. - The depth of a tree is the maximum level of any node in the tree or the path of a tree is the longest path from the root to a node. 106731499 -4- Binary Trees - General Definition of a binary tree: A Binary Tree is either empty, or it consists of a node called the root together with two binary trees called the left subtree ( left child ) and the right subtree ( right child ) of the root. A binary tree is a recursive structure. At any level n, a binary tree may n contain 1 to 2 nodes. The number of nodes in a tree relative to the depth of the tree contributes to the density. A degenerate tree - single leaf node and each non leaf has only one child.: Complete binary trees (each level has all possible nodes or all leaf nodes at the leftmost positions in the tree ) degenerate binary tree incomplete binary tree 106731499 -5- BINARY TREE NODES left left right right Left riright rightrigth CLASS tnode left right Left Declaration right right Left “d_tnode.h” // represents a node in a binary tree template <typename T> class tnode { public: // tnode is a class implementation structure. making the // data public simplifies building class functions T nodeValue; tnode<T> *left, *right; // default constructor. data not initialized tnode() {} // initialize the data members tnode (const T& item, tnode<T> *lptr = NULL, tnode<T> *rptr = NULL): nodeValue(item), left(lptr), right(rptr) {} }; right 106731499 -6- Building a binary tree // pointers to integer tree nodes tnode<int> *root, *p, *q, *r; // allocate leaf nodes with values 20 and 40 p = new tnode<int> (20); q = new tnode<int> (40); // allocate node at level 1 with value 30 // and left child 40 r = new tnode<int>(30, q, NULL); // allocate root node with value 10 and // children 20 (left) and 30 (right) root = new tnode<int>(10, p, r); 10 root 20 30 p r 40 q 106731499 -7- Traversal Methods Recursive Scanning Methods Recursive Tree Traversal - Different scan traversals are distinguished by the order in which they perform the actions at a node. N L R - Preorder Traversal template <typename T> void preorderOutput(tnode<T> *t, const string& separator = " { // the recursive scan terminates on a empty subtree if (t != NULL) { cout << t->nodeValue << separator; // output the node preorderOutput(t->left, separator); // descend left preorderOutput(t->right, separator);// descend right ") } } L N R - Inorder Traversal template <typename T> void inorderOutput(tnode<T> *t, const string& separator = " ") { // the recursive scan terminates on a empty subtree if (t != NULL) { inorderOutput(t->left, separator); // descend left cout << t->nodeValue << separator; // output the node inorderOutput(t->right, separator); // descend right } } L R N - Postorder Traversal template <typename T> void postorderOutput(tnode<T> *t, const string& separator = " ") { // the recursive scan terminates on a empty subtree if (t != NULL) { postorderOutput(t->left, separator); // descend left postorderOutput(t->right, separator); // descend right cout << t->nodeValue << separator; // output the node } } 106731499 -8- Tree Traversal A B C D E G 1. Preorder Traversal: 2. Inorder Traversal: 3. Postorder Traversal: H ABDGCEHIF DGBAHEICF GDBHIEFCA F I 106731499 -9- Binary Search Tree Binary Search Trees BINARY SEARCH TREE - Definition A binary search tree is a binary tree that is either empty or in which each node contains a key that satisfies the conditions: 1. All keys ( if any ) in the left subtree of the root precede ( al less than ) the key in the root. 2. The key in the root precedes ( is less than ) all keys (if any ) in its right subtree. 3. The left and right subtrees of the root are again binary search trees. Note: an inorder traversal visits the data in ascending order (Treesort) 25 10 37 15 30 65 Sample Binary Search Tree A binary search tree is easily implemented using linked storage. To search for a particular target the first comparison is made at the root of the tree. If the target is less than the key in the root the search continues with the left subtree. If the target is greater than the key in the root, the search continues with the right subtree. The function can be written using recursion or by writing a loop. The search terminates on an empty tree or if the target is found. Other key operations on a Binary Search Tree include insert(), erase(), and find() . 106731499 - 10 - #ifndef BINARY_SEARCH_TREE_CLASS #define BINARY_SEARCH_TREE_CLASS #ifndef NULL #include <cstddef> #endif // NULL #include #include #include #include #include <iomanip> <strstream> <string> <queue> <utility> #include "d_except.h" // for setw() // for format conversion // node data formatted as a string // pair class // exception classes using namespace std; // declares a binary search tree node object template <typename T> class stnode { public: // stnode is used to implement the binary search tree class // making the data public simplifies building the class functions T nodeValue; // node data stnode<T> *left, *right, *parent; // child pointers and pointer to the node's parent // constructor stnode (const T& item, stnode<T> *lptr = NULL, stnode<T> *rptr = NULL, stnode<T> *pptr = NULL): nodeValue(item), left(lptr), right(rptr), parent(pptr) {} }; // objects hold a formatted label string and the level,column // coordinates for a shadow tree node class tnodeShadow { public: string nodeValueStr; // formatted node value int level,column; tnodeShadow *left, *right; tnodeShadow () {} }; 106731499 - 11 - template <typename T> class stree { public: // include the iterator nested classes #include "d_stiter.h" stree(); // constructor. initialize root to NULL and size to 0 stree(T *first, T *last); // constructor. insert the elements from the pointer // range [first, last) into the tree stree(const stree<T>& tree); // copy constructor ~stree(); // destructor stree<T>& operator= (const stree<T>& rhs); // assignment operator iterator find(const T& item); // search for item. if found, return an iterator pointing // at it in the tree; otherwise, return end() const_iterator find(const T& item) const; // constant version int empty() const; // indicate whether the tree is empty int size() const; // return the number of data items in the tree pair<iterator, bool> insert(const T& item); // if item is not in the tree, insert it and // return a pair whose iterator component points // at item and whose bool component is true. if item // is in the tree, return a pair whose iterator // component points at the existing item and whose // bool component is false // Postcondition: the tree size increases by 1 if item // is not in the tree int erase(const T& item); // if item is in the tree, erase it and return 1; // otherwise, return 0 // Postcondition: the tree size decreases by 1 if // item is in the tree void erase(iterator pos); // erase the item pointed to by pos. // Preconditions: the tree is not empty and pos points // to an item in the tree. if the tree is empty, the // function throws the underflowError exception. if the // iterator is invalid, the function throws the // referenceError exception. // Postcondition: the tree size decreases by 1 void erase(iterator first, iterator last); // erase all items in the range [first, last). // Precondition: the tree is not empty. if the tree 106731499 - 12 // // // // is empty, the function throws the underflowError exception. Postcondition: the size of the tree decreases by the number of elements in the range [first, last) iterator begin(); // return an iterator pointing to the first item // inorder const_iterator begin() const; // constant version iterator end(); // return an iterator pointing just past the end of // the tree data const_iterator end() const; // constant version void displayTree(int maxCharacters); // tree display function. maxCharacters is the // largest number of characters required to draw // the value of a node private: stnode<T> *root; // pointer to tree root int treeSize; // number of elements in the tree stnode<T> *getSTNode(const T& item, stnode<T> lptr,stnode<T> *rptr, stnode<T> *pptr); // allocate a new tree node and return a pointer to it. // if memory allocation fails, the function throws the // memoryAllocationError exception stnode<T> *copyTree(stnode<T> *t); // recursive function used by copy constructor and ssignment // operator to assign the current tree as a copy of another ree void deleteTree(stnode<T> *t); // recursive function used by destructor and assignment // operator to delete all the nodes in the tree stnode<T> *findNode(const T& item) const; // search for item in the tree. if it is in the tree, // return a pointer to its node; otherwise, return NULL. // used by find() and erase() tnodeShadow *buildShadowTree(stnode<T> *t, int level, int& olumn) // recursive function that builds a subtree of the shadow ree // corresponding to node t of the tree we are drawing. level is // thelevel-coordinate for the root of the subtree, and column is // the changing column-coordinate of the tree nodes void deleteShadowTree(tnodeShadow *t); // remove the shadow tree from memory after displayTree() // displays the binary search tree }; 106731499 - 13 - template <typename T> stnode<T> *stree<T>::getSTNode(const T& item, stnode<T> *lptr,stnode<T> *rptr, stnode<T> *pptr) { stnode<T> *newNode; // initialize the data and all pointers newNode = new stnode<T> (item, lptr, rptr, pptr); if (newNode == NULL) throw memoryAllocationError("stree: memory allocation failure"); return newNode; } template <typename T> stnode<T> *stree<T>::copyTree(stnode<T> *t) { stnode<T> *newlptr, *newrptr, *newNode; // if tree branch NULL, return NULL if (t == NULL) return NULL; // copy the left branch of root t and assign its root to newlptr newlptr = copyTree(t->left); // copy the right branch of tree t and assign its root to newrptr newrptr = copyTree(t->right); // allocate storage for the current root node, assign // its value and pointers to its left and right subtrees. // the parent pointer of newNode is assigned when // newNode's parent is created. if newNode is root, // NULL is the correct value for its parent pointer newNode = getSTNode(t->nodeValue, newlptr, newrptr, NULL); // the current node is the parent of any subtree that // is not empty if (newlptr != NULL) newlptr->parent = newNode; if (newrptr != NULL) newrptr->parent = newNode; return newNode; } 106731499 // delete the tree stored by the current object template <typename T> void stree<T>::deleteTree(stnode<T> *t) { // if current root node is not NULL, delete its left subtree, // its right subtree and then the node itself if (t != NULL) { deleteTree(t->left); deleteTree(t->right); delete t; } } // search for data item in the tree. if found, return its node // address; otherwise, return NULL template <typename T> stnode<T> *stree<T>::findNode(const T& item) const { // cycle t through the tree starting with root stnode<T> *t = root; // terminate on on empty subtree while(t != NULL && !(item == t->nodeValue)) if (item < t->nodeValue) t = t->left; else t = t->right; // return pointer to node; NULL if not found return t; } template <typename T> stree<T>::stree(): root(NULL),treeSize(0) {} template <typename T> stree<T>::stree(T *first, T *last): root(NULL),treeSize(0) { T *p = first; // insert each item in [first, last) into the tree while (p != last) { insert(*p); p++; } } template <typename T> stree<T>::stree(const stree<T>& tree): treeSize(tree.treeSize) { // copy tree to the current object root = copyTree(tree.root); } - 14 - 106731499 template <typename T> stree<T>::~stree() { // erase the tree nodes from memory deleteTree(root); // tree is emtpy root = NULL; treeSize = 0; } template <typename T> stree<T>& stree<T>::operator= (const stree<T>& rhs) { // can't copy a tree to itself if (this == &rhs) return *this; // erase the existing tree nodes from memory deleteTree(root); // copy tree rhs into current object root = copyTree(rhs.root); // set the tree size treeSize = rhs.treeSize; // return reference to current object return *this; } template <typename T> stree<T>::iterator stree<T>::find(const T& item) { stnode<T> *curr; // search tree for item curr = findNode (item); // if item found, return const_iterator with value current; // otherwise, return end() if (curr != NULL) return iterator(curr, this); else return end(); } - 15 - 106731499 template <typename T> stree<T>::const_iterator stree<T>::find(const T& item) const { stnode<T> *curr; // search tree for item curr = findNode (item); // if item found, return const_iterator with value current; // otherwise, return end() if (curr != NULL) return const_iterator(curr, this); else return end(); } template <typename T> int stree<T>::empty() const { return root == NULL; } template <typename T> int stree<T>::size() const { return treeSize; } - 16 - 106731499 - 17 - template <typename T> pair<stree<T>::iterator, bool> stree<T>::insert(const T& item) { // t is current node in traversal, parent the previous node stnode<T> *t = root, *parent = NULL, *newNode; // terminate on on empty subtree while(t != NULL) { // update the parent pointer. then go left or right parent = t; // if a match occurs, return a pair whose iterator // component points at item in the tree and whose // bool component is false if (item == t->nodeValue) return pair<iterator, bool> (iterator(t, this), false); else if (item < t->nodeValue) t = t->left; else t = t->right; } // create the new leaf node newNode = getSTNode(item,NULL,NULL,parent); // if parent is NULL, insert as root node if (parent == NULL) root = newNode; else if (item < parent->nodeValue) // insert as left child parent->left = newNode; else // insert as right child parent->right = newNode; // increment size treeSize++; // return an pair whose iterator component points at // the new node and whose bool component is true return pair<iterator, bool> (iterator(newNode, this), true); } 106731499 - 18 - template <typename T> void stree<T>::erase(iterator pos) { // dNodePtr = pointer to node D that is deleted // pNodePtr = pointer to parent P of node D // rNodePtr = pointer to node R that replaces D stnode<T> *dNodePtr = pos.nodePtr, *pNodePtr, *rNodePtr; if (treeSize == 0) throw underflowError("stree erase(): tree is empty"); if (dNodePtr == NULL) throw referenceError("stree erase(): invalid iterator"); // assign pNodePtr the address of P pNodePtr = dNodePtr->parent; // If D has a NULL pointer, the // replacement node is the other child if (dNodePtr->left == NULL || dNodePtr->right == NULL) { if (dNodePtr->right == NULL) rNodePtr = dNodePtr->left; else rNodePtr = dNodePtr->right; if (rNodePtr != NULL) // the parent of R is now the parent of D rNodePtr->parent = pNodePtr; } // both pointers of dNodePtr are non-NULL. else { // find and unlink replacement node for D. // starting at the right child of node D, // find the node whose value is the smallest of all // nodes whose values are greater than the value in D. // unlink the node from the tree. // pOfRNodePtr = pointer to parent of replacement node stnode<T> *pOfRNodePtr = dNodePtr; // first possible replacement is right child of D rNodePtr = dNodePtr->right; // descend down left subtree of the right child of D, // keeping a record of current node and its parent. // when we stop, we have found the replacement while(rNodePtr->left != NULL) { pOfRNodePtr = rNodePtr; rNodePtr = rNodePtr->left; } 106731499 - 19 if (pOfRNodePtr == dNodePtr) { // right child of deleted node is the replacement. // assign left subtree of D to left subtree of R rNodePtr->left = dNodePtr->left; // assign the parent of D as the parent of R rNodePtr->parent = pNodePtr; // assign the left child of D to have parent R dNodePtr->left->parent = rNodePtr; } else { // we moved at least one node down a left branch // of the right child of D. unlink R from tree by // assigning its right subtree as the left child of // the parent of R pOfRNodePtr->left = rNodePtr->right; // the parent of the right child of R is the // parent of R if (rNodePtr->right != NULL) rNodePtr->right->parent = pOfRNodePtr; // put replacement node in place of // assign children of R to be those rNodePtr->left = dNodePtr->left; rNodePtr->right = dNodePtr->right; // assign the parent of R to be the rNodePtr->parent = pNodePtr; // assign the parent pointer in the // of R to point at R rNodePtr->left->parent = rNodePtr; rNodePtr->right->parent = rNodePtr; dNodePtr of D parent of D children } } // complete the link to the parent node. // deleting the root node. assign new root if (pNodePtr == NULL) root = rNodePtr; // attach R to the correct branch of P else if (dNodePtr->nodeValue < pNodePtr->nodeValue) pNodePtr->left = rNodePtr; else pNodePtr->right = rNodePtr; // delete the node from memory and decrement tree size delete dNodePtr; treeSize--; } 106731499 - 20 - template <typename T> int stree<T>::erase(const T& item) { int numberErased = 1; // search tree for item stnode<T> *p = findNode(item); // if item found, delete the node if (p != NULL) erase(iterator(p,this)); else numberErased = 0; return numberErased; } template <typename T> void stree<T>::erase(iterator first, iterator last) { if (treeSize == 0) throw underflowError("stree erase(): tree is empty"); iterator p = first; if (first == begin() && last == end()) { // we are asked to erase the entire tree. // erase the tree nodes from memory deleteTree(root); // tree is emtpy root = NULL; treeSize = 0; } else // erase each item in a subrange of the tree while (p != last) erase(p++); } template <typename T> stree<T>::iterator stree<T>::begin() { stnode<T> *curr = root; // if the tree is not empty, the first node // inorder is the farthest node left from root if (curr != NULL) while (curr->left != NULL) curr = curr->left; // build return value using private constructor return iterator(curr, this); } 106731499 - 21 - template <typename T> stree<T>::const_iterator stree<T>::begin() const { const stnode<T> *curr = root; // if the tree is not empty, the first node // inorder is the farthest node left from root if (curr != NULL) while (curr->left != NULL) curr = curr->left; // build return value using private constructor return const_iterator(curr, this); } template <typename T> stree<T>::iterator stree<T>::end() { // end indicated by an iterator with NULL stnode pointer return iterator(NULL, this); } template <typename T> stree<T>::const_iterator stree<T>::end() const { // end indicated by an iterator with NULL stnode pointer return const_iterator(NULL, this); } // recursive inorder scan used to build the shadow tree template <typename T> tnodeShadow *stree<T>::buildShadowTree(stnode<T> *t, int level, int& column) { // pointer to new shadow tree node tnodeShadow *newNode = NULL; // text and ostr used to perform format conversion char text[80]; ostrstream ostr(text,80); if (t != NULL) { // create the new shadow tree node newNode = new tnodeShadow; // allocate node for left child at next level in tree; attach node tnodeShadow *newLeft = buildShadowTree(t->left, level+1, column); newNode->left = newLeft; // initialize data members of the new node ostr << t->nodeValue << ends; // format conversion newNode->nodeValueStr = text; newNode->level = level; newNode->column = column; 106731499 - 22 // update column to next cell in the table column++; // allocate node for right child at next level in tree; attach node tnodeShadow *newRight = buildShadowTree(t->right, level+1, column); newNode->right = newRight; } return newNode; } template <typename T> void stree<T>::displayTree(int maxCharacters) { string label; int level = 0, column = 0; int colWidth = maxCharacters + 1; // int currLevel = 0, currCol = 0; if (treeSize == 0) return; // build the shadow tree tnodeShadow *shadowRoot = buildShadowTree(root, level, column); // use during the level order scan of the shadow tree tnodeShadow *currNode; // store siblings of each tnodeShadow object in a queue so that // they are visited in order at the next level of the tree queue<tnodeShadow *> q; // insert the root in the queue and set current level to 0 q.push(shadowRoot); // continue the iterative process until the queue is empty while(!q.empty()) { // delete front node from queue and make it the current node currNode = q.front(); q.pop(); // if level changes, output a newline if (currNode->level > currLevel) { currLevel = currNode->level; currCol = 0; cout << endl; } // if a left child exists, insert the child in the queue if(currNode->left != NULL) q.push(currNode->left); 106731499 - 23 - // if a right child exists, insert the child in the queue if(currNode->right != NULL) q.push(currNode->right); // output formatted node label if (currNode->column > currCol) { cout << setw((currNode->column-currCol)*colWidth) << " "; currCol = currNode->column; } cout << setw(colWidth) << currNode->nodeValueStr; currCol++; } cout << endl; // delete the shadow tree deleteShadowTree(shadowRoot); } template <typename T> void stree<T>::deleteShadowTree(tnodeShadow *t) { // if current root node is not NULL, delete its left subtree, // its right subtree and then the node itself if (t != NULL) { deleteShadowTree(t->left); deleteShadowTree(t->right); delete t; } } #endif // BINARY_SEARCH_TREE_CLASS 106731499 - 24 - #ifndef VIDEO_CLASS #define VIDEO_CLASS #include <iostream> #include <string> using namespace std; class video { public: // constructor. initialize film title and numCopies video(const string& film = "", int copies = 1): filmTitle(film), numCopies(copies) {} // add n to the number of copies. note that if n < 0 // the function decreases the number of copies void updateCopies(int n){ numCopies += n; } // return the number of copies of the film title int getCopies() { return numCopies; } // two video objects are "equal" if they have the same title friend bool operator== (const video& lhs, const video& rhs) { return lhs.filmTitle == rhs.filmTitle; } // compare video objects by comparing film titles friend bool operator< (const video& lhs, const video& rhs) { return lhs.filmTitle < rhs.filmTitle; } // output a video object friend ostream& operator<< (ostream& ostr, const video& obj){ ostr << obj.filmTitle << " (" << obj.numCopies << ")" ; return ostr; } private: // title of the film string filmTitle; // number of copies (>= 0) int numCopies; }; #endif // VIDEO_CLASS 106731499 // // // // // // // // // // // // // // // // // // // // // // // - 25 - File: prg10_5.cpp the program simulates inventory maintenance for a video store. the program stores the title of a film and the number of copies the store owns in a video object. the video class has functions that access and update the number of copies, compare objects by title, and output a title and the number of copies. the function setupInventory() inputs film titles from the file "films.dat" and creates the stree object inventory of video data. after listing the films in the inventory, in an interactive loop the clerk inputs whether the customer wishes to rent a film, return a film, or whether business is over for the day. when a customer rents a film, the program updates the inventory by reducing the number of copies of the film by 1, and adds the film to the stree object rentals that maintains a database of rented films. when a customer returns a film, the program removes 1 copy of the film from the rentals object and increases the number of copies in inventory by 1. at the end of the business day, the program outputs the list of rented films and the films remaining in the inventory #include #include #include #include <iostream> <fstream> <string> <utility> #include "d_stree.h" #include "d_video.h" #include "d_util.h" // for pair class // stree class // video class // for writeSTree() using namespace std; // initialize inventoryList from file "films.dat" void setupInventory(stree<video>& inventory); // process the return of a film void returnTransaction(stree<video>& inventory, stree<video>& rentals, const string& filmName); // process the rental of a film void rentalTransaction(stree<video>& inventory, stree<video>& rentals, const string& filmName); 106731499 - 26 - int main() { // the inventory and rental lists stree<video> inventory, rentals; // assign return value from find() to filmIter stree<video>::iterator filmIter; // input from store operator. transactionType = "Rent", "Return", // or "Done" string transactionType; // film requested by a customer string filmName; // read and output inventory file setupInventory(inventory); cout << "Initial inventory list:" << endl; writeSTree(inventory, "\n"); cout << endl; // process customers by entering "Rental" or "Return" // followed by the film name or "Done" to end the program. // for "Rent", decrease number of copies in inventory by 1 // and add the copy to the rental database. for "Return", // remove copy from rental database and increase number // of copies in inventory by 1 cout << "Transactions: Enter type (Rent, Return, Done)" << endl; cout << "followed by film name or space if done" << endl << endl; while (true) { // input the transaction type. the input must terminate with // a blank cout << "Transaction: "; getline(cin, transactionType, ' '); // if "Done", terminate the loop if (transactionType == "Done") break; getline(cin, filmName,'\n'); // get film name if (transactionType == "Return") returnTransaction(inventory, rentals, filmName); else rentalTransaction(inventory, rentals, filmName); } cout << endl; // output the final rental and inventory lists. cout << "Rented Films: " << endl << endl; writeSTree(rentals, "\n"); cout << endl; cout << "Films Remaining in Inventory:" << endl << endl; writeSTree(inventory, "\n"); return 0; } 106731499 - 27 - void setupInventory(stree<video>& inventory) { ifstream filmFile; // input stream string filmName; // individual file names // use with stree insert() pair<stree<video>::iterator, bool> p; // open the file "films.dat" filmFile.open("films.dat"); if (!filmFile) { cerr << "File 'films.dat' not found!" << endl; exit(1); } // read lines until EOF; insert names in inventory list while(true) { getline(filmFile,filmName,'\n'); if (!filmFile) break; // try an insertion with default of 1 copy p = inventory.insert(video(filmName)); // see if video already in the inventory if (p.second == false) // it is in the inventory. increment number of copies (*(p.first)).updateCopies(1); } } void returnTransaction(stree<video>& inventory, stree<video>& rentals, const string& filmName) { stree<video>::iterator filmIter; // locate the film in the return database filmIter = rentals.find(video(filmName)); // if there is only 1 copy left, erase the entry; // otherwise, decrease the number of rented copies // by 1 if ((*filmIter).getCopies() == 1) rentals.erase(filmIter); else (*filmIter).updateCopies(-1); // locate the film in the inventory and increase the // number of copies available by 1 filmIter = inventory.find(video(filmName)); (*filmIter).updateCopies(1); } 106731499 - 28 - void rentalTransaction(stree<video>& inventory, stree<video>& rentals, const string& filmName) { stree<video>::iterator filmIter; // use pObj with stree insert() pair<stree<video>::iterator,bool> pObj; // is film available? filmIter = inventory.find(video(filmName)); if ( filmIter == inventory.end()) // film is not in the store's inventory cout << "Film " << filmName << " is not in inventory" << endl; else if ((*filmIter).getCopies() == 0) // all copies are checked out cout << "All copies of " << filmName << " are checked out" << endl; else { // decrease the number of copies in the inventory // by 1 (*filmIter).updateCopies(-1); // attempt to insert the film into rentalList. if it is // inserted, the number of copies will be 1 pObj = rentals.insert(video(filmName)); // if film not inserted, increase number of rented copies // by 1 if (pObj.second == false) (*(pObj.first)).updateCopies(1); } } /* Run: Initial inventory list: Frequency (1) Gladiator (2) Lord of the Rings (4) U-571 (2) Transactions: Enter type (Rent, Return, Done) followed by film name or space if done Transaction: Rent Gladiator Transaction: Rent Frequency Transaction: Rent Shaft Film Shaft is not in inventory Transaction: Rent Frequency All copies of Frequency are checked out Transaction: Done Rented Films: Frequency (1) 106731499 Gladiator (1) Films Remaining in Inventory: Frequency (0) Gladiator (1) Lord of the Rings (4) U-571 (2) */ - 29 - 106731499 - 30 - Associative containers Associative containers store and retrieve data by value rather than by position. A set is a collection of keys where each key is unique. A map is a collection of key-value pairs that associate a key with a value. In a map there is only one value associated with a key. A map is often called an associative array because applying the index operator with the key as its argument returns the value associated with the key. Multimaps and multiset containers allow multiple occurrences of a key The STL set class uses one template argument to refer to the key type. The STL map class requires two template arguments (one for the key and the other for the value type) A binary search tree is an associative structure and is ideal for the implementation of sets and maps.