* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Download Chapter x - CHAPTER TITLE
Survey
Document related concepts
Transcript
Chapter 17 Linked Data Structures 0. Introduction Linked data structures in C++ may be implemented using pointers and dynamically allocated memory. Linked data structures may also be implemented using one array of records, or one or more arrays, where the items in the arrays with the same index serve as the record. An array of index values serves as the pointer to other records. If the array is dynamically allocated, either of these can grow or shrink as needed. The array technique is infrequently useful. We will not develop it here. Our interest in this chapter will be the implementation of linked lists using pointers and dynamically allocated memory and other non-list like linked structures such as trees. Linked structures encapsulate data and behavior in classes (or structs, that is a matter of taste). Some functions in the implementation will need access to the encapsulated data. The text points out in the introduction that there are three ways to approach this problem. Use the C style where everything important gravitates to global or public. Use a C++ styles with classes, making member variables private, but using accessor and mutator functions. The mutators should ensure that changes to class variables cause class invariants remain true. Use friends, protected inheritance and composition (class object members). The knowledgeable reader is aware of the Standard Template Library. One might ask why should we bother to study linked structures when such libraries are available. (One might ask the same question about low-level arrays.) The simple answer is that the library is wonderful but does not cover all needs. The programmer will be faced with situations where there is a need to define data structures specific to the application. The STL will not substitute for programming techniques that are learned by studying linked lists (and low level arrays), and solving problems using these tools. Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 2 1. Outline of topics in the chapter 17.1 Nodes and Linked Lists Nodes Linked Lists Inserting an Node at the Head of a List Inserting and Removing Nodes Inside a List Searching a Linked List 17.2 Linked List Application Examples: A Stack Template Class Example: A Queue Template Class Friend Classes and Similar Alternatives 17.3 Iterators Pointers as Iterators Iterator classes Example: An Iterator Class 17.4 Trees Tree Properties Example: A Tree Template Class 2. General remarks on the chapter 17.1 Nodes and Linked Lists A linked list is a struct or class with optional data variables and a pointer variable able to point to an object of this class or struct type. The structures are self-referential. We use the term dynamic data structures because the objects is (usually) dynamically allocated using the new operator. The objects are called nodes. Nodes A node in a linked structure contains (at a minimum) the data and one or more links that contains one or more references to other nodes of the same type, or a value that indicates Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 3 that the link does not refer to any node. Thus, the definition of a node is self-referential1. We saw another form self-reference, recursion, in Chapter 13. In C++, nodes are implemented as a struct or class with whatever data and function members the design requires and a reference to other nodes. The reference is implemented as a pointer to a class or struct of the same type. We could declare a node of the following type. struct Node { int data; Node * link; }; typedef Node* NodePtr; NodePtr head; head = new Node; Here we have a node that contains int data and a pointer of type Node*, to point to a following node. Aside on typedef: Our author recommends that a type definition be used to define an identifier to carry the type information, "pointer to node". Type definitions are useful to conceal much nasty syntax detail. Nevertheless, you should caution the student that great care is needed in the selection of names to be defined in a type definition, and that few programmers are good at it. Always remember that declaring a pointer allocates only (automatic) space for the pointer, but not space for the node to which the pointer can point. Allocating memory from the heap is the programmer’s responsibility. One way to access to the data member of the node is to first dereference the pointer to get a node, then use the dot member access operator. *head.data = 3; Unfortunately, this fails because the precedence of the dot operator is greater than the precedence of the 1 Much that is interesting is self-referential in the law, art, music, biology, and computing. Douglas Hofstadter examines much that is self-referential in Goedel, Escher, Bach Vintage Books, 1980. Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 4 Access to the data members is done via the -> operator: head->data = 3; head->link = NULL; It is very important that the student understand that linked lists have this in common with strings: there is a NULL terminator for each. Even though they are spelled differently, they are both 0. (‘\0’ is one spelling for 0, and is a character. NULL is another spelling for 0; this one is an integer, not a pointer. Look it up in cstdlib. ) Inserting a Node at the Head of a List The first thing to point out to the student is that a sequence of well-executed sketches of the linked list and the insertion task will save a vast amount of time in debugging later. It is much more convenient to add the node at the Head, since we already have a pointer pointing to the Head. Of course we can add a node to a list at the end of a list, but this requires that we know where that is. We either have to maintain a pointer to the end of the list or traverse the list (examine nodes one by one) until we find the end, that is, a node with a link pointer having a NULL value. The text points out that adding a node to the head of the list is easy: Create the node. Initialize the data field. Make the link pointer field point at the existing list (the node to which the Head pointer points). Finally, move the head pointer to point at the new node. The sequence of steps as code looks like this: // Head already points to an already constructed NULL // terminated linked list NodePtr temp; temp = new Node; temp->data = // the new data, whatever it is, goes here temp->link = Head; Head = temp; Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures temp 12 Page 5 Head 15 3 NULL temp 12 Head 5 3 NULL Head is made to point to the newly allocated node. temp Head 12 15 3 NULL PITFALL Losing Nodes If you assign to a pointer that already points to free store before deleting that store, as my students say, “... you blew it.” Suppose you assigned Head the value of temp before assigning temp’s link filed. There is nothing pointing to the rest of the list, and you have committed an error. If this is only a few bytes, the memory loss is nothing to worry about. However, the data loss may be serious. Further, such losses tend to occur in loops, where thousands, even hundreds of thousands of bytes can be chopped off and lost. See Display 17.5 in the text. I have pointed out, and the text points out, memory lost in this manner is unavailable to your program. Worse, the memory is unavailable to any program running concurrently on Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 6 your system (even operating system programs). The result is a system that acts as if it had this much less memory than what you paid for. The operating system slows to a crawl, or crashes, depending on the robustness of the operating system. Please caution students about the antisocial effects of memory leaks, not to mention lowered grades that can result. ;) Searching a Linked List A summary of the linked list search follows. Here we declare a “visit” pointer. We make it point at the head node. Then we determine whether the data member is equal to the target. If so return the pointer to the node. Otherwise, we make the visit pointer point at the link member of the visit pointer. We do this repeatedly, while the visit pointer is not NULL. This is only slightly different from the text’s algorithm. The difference is where the loop is exited. My discussion suggests testing at the beginning of the loop for a NULL pointer, and the exit is in the middle of the loop, when the target is found. There is no particular advantage to either way. struct Node { int data; Node* link; }; typedef Node* NodePtr; NodePtr search( NodePtr head, int target) { NodePtr visit = head; while ( NULL != visit ) { if ( visit->data == target ) return visit; visit = visit->link; } return NULL; } Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 7 The author’s approach is usually slightly different from mine. I usually stick closely to the C/C++ idiom, while he is a bit more logical than I. I present alternatives here, in a forum for the instructor. Inserting and Removing Nodes Inside a List If we have tools to insert at any place in a list, and tools to search the list for a location where a new item should go, then we can keep a list sorted as data comes in at random. To insert in a list, we need a pointer to the node that should have the newly created node after it. The text calls this the afterMe pointer. Then assign a newly allocated (with new) node to be inserted to the tempPtr, and initialize the data fields. The order of the next assignments is critical to preventing losing the tail of list. Make the link field of the node pointed to by tempPtr point to the same place the link pointer of the afterMe pointer. Then the afterMe link member can be assigned the value of the tempPtr. Saying this is just horrible. It is much clearer in pictures and code. Head First, find where node goes afterMe tempPtr Fourth Second, create, reassign initialize node afterMe to be inserted Third, make the temp pointer point where afterMe’s link points. Code: NodePtr afterMe = search(head, target); // First NodePtr tempPtr = new Node; // Second tempPtr->data = data_to_be_added; tempPtr->link = afterMe->link; // Third afterMe->link = tempPtr; // Fourth The text has complete code for a linked list in Display 17.9. Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 8 PITFALL: Assigning Head Pointers Please emphasize this pitfall. It is of critical importance that the students understand that when a pointer variable is assigned data is not moved in the linked list. Rather, when pointer variables are assigned, the values of the pointer variable itself changes, that is, the place to which the pointer variable points is changed. If head1 points to a linked list and head2 points to another linked list, merely assigning one pointer to another will orphan the free store pointed to by the head of the list that is the l-value in the assignment, causing a memory leak. head1 = head2; // orphans store pointed originally by head1. If it is necessary to assign a head pointer, you must either save the value of the pointer in another pointer variable, or walk through the list, deleting the store, node by node, if you are through with it. If you are not through with that memory, you should reassess your desire to assign the head pointers. That is not the end of the difficulty. Remember our discussion of aliases? After assigning pointers, as is done here, you have two variables that refer to the same collection of memory locations. Changing one of these lists changes the (same) list pointed to by the other. The author says it clearly: Remember there is only one list, not two. 17.2 Linked List Application Linked structures have application wherever there is a need to allocate only the memory needed as needed, and a need to reclaim memory as soon as it is no longer needed. Other linked structures may or may not have this property. For example, a binary search tree (17.4) can have access, insertion and deletion times as fast as proportional to the natural logarithm of the number of nodes in the tree. Suppose a list contains N items. Then the run time to find an item in a linked list containing N items is O(N). This is balanced by O(1) insertion and deletion of an item anywhere in the list.. The text implements a stack template class and a queue template class as linked lists. Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 9 Friend Classes and Similar Alternatives Recall the issues of encapsulated data access that we mentioned in the introduction. There is encapsulation but no hiding of anything if we choose to use the C style of programming. On the other hand, if we use classes with private data, use of accessor and mutator functions can be annoying. Worse, the use of mutators can be downright dangerous if the mutator doesn't test the data to maintain the class invariant. In fact, the use of mutators that do not verify data actually breaks the encapsulation that those who object to friend status desire. There is a concern about the breaking the encapsulation by granting friend status. If the function to which the class grants friend status can be trusted, use of friends is safe. If friend status is not granted, then overloaded operators for i/o such as << and >> cannot be overloaded so that they operate in the conventional fashion. To obtain the following (conventional) behavior without friend function overloading, operator<< and operator<< must be overloaded as a members of the iostream class to get the following use. cout << object; We do not have access to this library class to carry out this overloading. Overloading operators such as + with a friend where we want symmetric behavior, such as 2 + object or object + 2 requires friend overloading so that programmer provided conversion will be used. Our conclusion is that friend functions are useful if the friend function is trusted. (Presumably the friend function can be trusted because it is the class author who grants friendship.) Certain operator overloading behavior cannot be obtained if friend functions are proscribed. Mutator and accessor functions are useful, provided the data is verified by the mutator function. Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 10 It is possible to declare a class member function as a friend of another class. The syntax is exactly the same as any other friend function. The only difference is that its class name and the scope resolution operator must qualify the function name. class A { public: void f() {}; }; class B { public: friend void A::f(); // grants A::f() access to B's private b. private: int b; }; All the member functions of class A can be granted access to the private or protected data and members of another class B by declaring A to be a friend class of class B. class A { public: //other member functions private: int a; }; class B { public: friend class A; // grants all A's function members access to b // other members private: int b; }; Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 11 If the classes are listed in order reverse to the above, then we are attempting to use class A members in class B prior to definition. A forward definition of class A prior to the definition of class B is sufficient to make members of class A available to class B members. Note that not all compilers require this forward declaration. Nevertheless, it should be used. class A; // a forward declaration. class B { public: friend class A; // other members of B that need access to A's members }; class A { public: //other members }; However, you cannot declare an object of class A before the definition of class A has been seen. Here is a simple example of a declaration of one class template as a friend of another class template. C++ requires a forward declaration for class B to grant the class A friend status. #include <iostream> using namespace std; template<class T> // forward declaration class A; template<class T> class B { public: friend class A<T>; Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures B(); // other member functions private: T b; // other private members }; template<class T> B<T>::B() : b(0) { cout << "B()\n"; } template<class T> class A { public: A(); void aFunc(); //other members }; template<class T> void A<T>::aFunc() { cout << "aFunc()\n"; B<int> x; cout << "B object x's member b, x.b = " << x.b << endl; } template<class T> A<T>::A() { cout << "A()\n"; } int main() { Page 12 Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 13 cout << "main() before declaring B object\n"; A<int> w; w.aFunc(); cout << "main() after declaring B object\n"; return 0; } 17.3 Iterators The iterator construct allows access to successive elements of a container without compromising the container data or structure. Iterators are frequently objects of an iterator class2. There is frequent need to access each item in a container in some order. This may be the order of insertion , sorted or another order. The terms cycling through or traversing the container are used to describe this process. It is important to conceal the internals of the container to prevent corruption of the data or of container structure. Pointers as Iterators The text points out that the prototypical iterator is the pointer. However, not all properties of pointers are appropriate for linked lists. If we are using a linked list, we cannot use the ++ operator on a pointer to one node of a linked list to get the iterator to refer to the next node. We must follow the linked list instead: If iterator is a pointer to a node in a linked list, then we can cycle through the linked list using the following code fragment. for(iterator = head; iterator != NULL; iterator = iterator->link) Do whatever task you need with node pointed to by iterator It is important to note that the increment portion of this for loop is not the ++ operator, rather it is code to step along the linked list. Observe that pointers do exactly what we want when the container is an array. An iterator of type pointer to array base type, initialized point to the start of the array, can be 2 Iterators have application wherever we have containers. In Chapter 19, we will see that the Standard Template Library defines iterators for each of its containers. Many of these iterators are defined as classes. Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 14 incremented using ++ to point to the next element. This iterator has the property that the result of adding an integer to an iterator has iterator value that (if it still points into the array) points to an element that is the integer number of elements from the starting point. The direction depends on the sign of the number. Because iterators do not usually have properties we need for our containers, we frequently use iterator classes. Iterator classes Using a class to implement the iterator for a given container, we can provide machinery that allows iterators to behave as if they really were pointers by overloading operators. Clearly we could implement all the properties of a pointer with an array class. However, many of these properties cannot be implemented efficiently, so we do not implement those properties. Examples of properties that we do not implement for iterators into linked list classes are adding an integer value to the iterator and indexing. Example: An Iterator Class An ordinary queue can only be changed by adding an element at the rear, or by removing an element at the front, and can be accessed only by fetching a copy of the element at the front of the queue. There are applications that benefit from access to elements in a queue other than the element at the front. This example provides such a queue3. 17.4 Trees The test points out that detailed treatment of trees is beyond the scope of the text, and I will not attempt it here. I present a discussion of the ideas with a slightly different feel than the text's to indicate how I teach this material. Tree Properties There is a of vocabulary that the student must learn in order to be able to read the textbook and other literature that deals with trees. I usually present this vocabulary material in the following manner, along with many blackboard sketches. The definition of 3 I call such a queue as a glass queue, because you can see into the queue. Analogously, a glass stack glass stack could be defined with iterators. Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 15 tree can be made quite complicated. I have tried to present as simple a definition as I can find. It is well to make the language and drawings as clear and as suggestive of the computer science ideas as possible. My definitions follow. A path in an arbitrary linked structure4 is a sequence of nodes encountered when we follow links starting at some node in the structure. If there exists a path from node A to a node B, then the node A is a predecessor of node B, and B is the successor of A. If we start at some node, follow a link out of that node directly into the next node, then the first node is the immediate predecessor node, and the next node is the immediate successor node. Using this language, we can define a tree as a linked structure for which there is at most one path from any node to any other node and that has a unique node with no predecessor (or the tree is empty)5. Clearly, in a tree of more than a two nodes, there may be pairs of nodes for which there is no path from one node to the other. In a tree, if node A is the immediate predecessor of node B then we say that A is the parent of B and B is the child of A. A node with no children is called a leaf. (The plural is leaves, not leafs.) We note that the root of a tree is the only node having no parent. It will be important when we get to tree traversal to think of leaves as having empty, or null, children. We say node D is a descendant of node A in a tree if there is a path from A to D. If node D is a descendant of node A then A an ancestor of D. Each node, say A, in a tree defines a subtree that is a tree that is part of the larger tree and has the node A as root. A binary tree has two links at each node so that it can have at most two children at any node. In a binary tree, we designate one of the two children as the left child and the other as the right child. The left child of a node is the root of the left subtree of the tree rooted at the node. Similarly, the right child of a node is the root of the right subtree of the tree rooted at the node. 4 A mathematician would call this a directed graph. I avoid the term in my class unless I am asked about it. If we do not require the root to be unique we leave open the possibility of a multiple structures that I call a forest. If we do not require a unique path, we allow a path to come together forming a loop. Neither of these situations is intuitively a tree. 5 Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 16 Trees in computer science are usually drawn with the root upward and its leaves downward. There is always a pointer variable of type pointer to node that points to the root of the tree. The nodes of a tree, like the nodes of any linked structure, usually have data stored in them. The purpose of the tree is to organize the data so that it can be accessed conveniently for processing. There are several ways to traverse the node of a tree. These are Preorder, In-order and Post order processing. We define each traversal method recursively. In presenting these traversal algorithms, I normally draw a full tree with numbers 1 through 7 for the data of the nodes, where the sequence is either preorder or in-order. 1 2 3 5 4 6 7 Preorder Process a tree 1. Process the data in the root node 2. Process the left subtree 3. Process the right subtree In-order Process a tree 1. Process the left subtree 2. Process the data in the root node 3. Process the right subtree Post order Process a tree 1. Process the left subtree 2. Process the right subtree 3. Process the data in the root node Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 17 For purposes of the following traversals, "process the data in the root node" does nothing but list the data in the node. A preorder traversal of this tree yields 1, 2, 3, 4, 5, 6, 7. An in-order of the traversal yields 3, 2, 4, 1, 6, 5, 7. A post order traversal yields 3, 4, 2, 6, 7, 5, 1. An Approach to Nonrecursive Traversals If you think of examining a node, noting that this is the first time the node has been seen. Next look at the left child, if any. Note that this is the first time this node has been seen. Continue until a leaf is found while traversing down the left children. The leaves can be thought of as having empty left and right subtrees. Upon return from traversing the empty subtree of the node containing 3, we have seen this node the second time. Then we traverse the empty right subtree. After this we have seen the node the third time. Then back up to the immediately preceding node, and if there is a right subtree, traverse this subtree using exactly this process. This process will examine and keep record of the number of times we have inspected each node. To carry out a preorder traversal, process the node the first time it is seen, for an in-order traversal, process the node the second time it is seen, and for post order traversal, process the node the third time it is seen. Example: A Tree Template Class I have no remarks on this example. 3. Solutions to, and remarks on, selected Programming Projects 1. Reverse. Write a void function that reverses a linked list of integer values. The text calls for a reference parameter of type pointer to node, and suggests rearranging the nodes in the list. // file 17prg1.cc // For Chapter 17 Programming Problem #1: // Write a void function that accepts a linked list of integers and // reverses the order of its nodes. The function will have one call // by reference parameter that is a pointer to the head of the list. Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 18 // After the call, this pointer will point to a list that has the // same nodes in reverse order of the original list. This function // does not allocate nor delete store, but only rearranges the nodes. // The easiest way is to reverse the list is to traverse the list // while stacking pointers using a loop. #include <iostream> #include "listtools.h" #include "listtools.cpp" #include "stack.h" #include "stack.cpp" using namespace StackSavitch; using namespace LinkedListSavitch; typedef Node<int>* NodePtr; void reverse( NodePtr head ); int main( ) { using namespace LinkedListSavitch; using namespace std; int number = 0; NodePtr temp, head = NULL; // Build linked list from user entered values. cout << "enter numbers, negative value quits" << endl; cin >> number; while( number > 0 ) { headInsert(head, number); cin >> number; } Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 19 // Display list temp = head; cout << "list as entered starting at last item entered. " << endl; while( NULL != temp ) { cout << temp->getData() << endl; temp = temp->getLink(); } cout << "calling reverse" << endl; reverse(head); temp = head; cout << "list reversed: " << endl; // display reversed list. while( NULL != temp ) { cout << temp->getData() << endl; temp = temp->getLink(); } return 0; } void reverse( NodePtr head ) { Stack<int> stack; NodePtr temp; temp = head; while ( NULL != temp ) { stack.push( temp->getData()); temp = temp->getLink(); } Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 20 temp = head; while ( NULL != temp ) { temp->setData(stack.pop()); temp = temp->getLink(); } } No test run is provided 2. MergeLists Merge two sorted linked lists so the result is sorted. I noticed one anomaly that the student should be warned about. There is an interaction between the stack template and the linked list template that prevents a call to void headInsert(Node<T>* head, const T& theData); from deducing the template for the first template parameter when I had global using namespace statement for both LinkedListSavitch and for StackSavitch namespaces. When I placed the using namespace StackSavitch within the reverse function (the only one that uses the Stack) the problem went away. I did not investigate the problem once I found a workaround. This again emphasizes the need to have using definitions and declaration of names from the namespaces declared in as small a scope as possible. //2. MergeLists // The function mergeLists is to take two call by reference parameters // that are pointer variables that point to the heads of two linked // lists // already of values of type int. The linked lists are assumed to be sorted. // The smallest in each list is at the head, increasing along the // list). // The function returns a pointer to the head of a new list that is the // rearranged nodes of the combined lists. The resulting list is to be // sorted, smallest at the head, and increasing along the list. Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 21 // When the function terminates, the two arguments should have the // value NULL (0). Note that this solution neither creates nor // destroys nodes. // Using the reference to pointer parameters to walk through the lists, // determine which head node element is smaller, hook that node to the // head pointer for the return value, compare the one that is left to // the next node from the list that was last used. Continue until the // different to the smaller list is NULL. Run out the larger list, // hooking its nodes to the head pointer for the return value. // This results in a list that is sorted with the largest at the head // and decreasing as we move along the list. Then return Head. #include <iostream> #include "stack.h" #include "stack.cpp" #include "listtools.h" #include "listtools.cpp" using namespace LinkedListSavitch; typedef Node<int>* NodePtr; // Precondition: head1 and head1 point to linked lists of Node // class objects that are each sorted, each head pointer pointing to // the smallest item in the list. // Postcondition: head points to a list of nodes taken from both // lists, sorted. head points to the smallest in the combined list. // The pointers head1 and head2 are NULL. NodePtr mergeLists( NodePtr & head1, NodePtr & head2) { NodePtr head, follow, smaller; // if either list is NULL, return the other head. Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures if( NULL == head1 && NULL != head2 ) { head = head2; head2 = NULL; return head; } if( NULL == head2 && NULL != head1 ) { head = head1; head1 = NULL; return head; } if( NULL == head2 && NULL == head1 ) return NULL; // now get one node attached to head... if ( NULL != head1 && NULL != head2 ) { if ( head2->getData() > head1->getData() ) { head = head1; // smaller node to front of head's head1 = head1->getLink(); } else { head = head2; // as above, for head2 head2 = head2->getLink(); } } follow = head; // connect the rest of them. while ( NULL != head1 && NULL != head2 ) { Page 22 Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 23 if ( head2->getData() > head1->getData()) { smaller = head1; // put smaller node on head's list head1 = head1->getLink(); } else { smaller = head2; // as above, for head2 head2 = head2->getLink(); } follow->setLink(smaller); follow = smaller; } // one of head1 or head1 is NULL. Must run out the other list if (NULL != head2 ) follow->setLink(head2); if(NULL != head1 ) follow->setLink(head1); head1 = NULL; head2 = NULL; return head; } void reverse( NodePtr head ) { using namespace StackSavitch; Stack<int> stack; NodePtr temp; temp = head; while ( NULL != temp ) { Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 24 stack.push( temp->getData()); temp = temp->getLink(); } temp = head; while ( NULL != temp ) { temp->setData(stack.pop()); temp = temp->getLink(); } } int main() { using namespace std; int number = 0; NodePtr head, head1, head2, temp; head = head1 = head2 = temp = NULL; // NOTE WELL the necessity of this initialization. cout << "enter increasing numbers for list1 , negative quits" << endl; cin >> number; while( number > 0 ) { headInsert(head1, number); cin >> number; } reverse(head1); temp = head1; Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 25 cout << "list1: " << endl; while( NULL != temp ) { cout << temp->getData() << endl; temp = temp->getLink(); } cout << "enter increasing numbers for list2, negative quits" << endl; cin >> number; while( number >0 ) { headInsert(head2, number); cin >> number; } reverse(head2); temp = head2; cout << "list2: " << endl; while( NULL != temp ) { cout << temp->getData() << endl; temp = temp->getLink(); } head = mergeLists(head1, head2); temp = head; cout << "merged list, front to rear: " << endl; while( NULL != temp ) { cout << temp->getData() << endl; temp = temp->getLink(); Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 26 } return 0; } /* Runs testing this have one list larger, then the other list larger, then equal sizes. I had some trouble with non-symmetric data, so I tested each of the 3 ways. enter increasing numbers for list1 , negative quits 1 3 5 -1 list1: 1 3 5 enter increasing numbers for list2, negative quits 2 4 -1 list2: 2 4 merged list, front to rear: 1 2 3 4 5 enter increasing numbers for list1 , negative quits 1 3 -1 list1: 1 3 enter increasing numbers for list2, negative quits Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures 2 4 6 8 -1 list2: 2 4 6 8 merged list, front to rear: 1 2 3 4 6 8 enter increasing numbers for list1 , negative quits 1 3 5 -1 list1: 1 3 5 enter increasing numbers for list2, negative quits 2 4 6 -1 list2: 2 4 6 merged list, front to rear: 1 2 3 4 5 6 */ Page 27 Instructor’s Resource Manual for Savitch Absolute C++ 04/30/17 Chapter 17 Linked Data Structures Page 28 3. Polynomial – A space efficient linked list implementation. No solution is provided for this problem. 4. Search Tree Template No solution is provided for this problem. A) Add default and copy constructor, delete (?), overloaded operator =, makeEmpty, height, size preOrderTraversal, inOrderTraversal, postOrderTraversal. Add global function Process to process node data. The function Process is a friend of the SearchTree . For this exercise, it is only a stub. Supply pre- and post- conditions for each function. Note: Though the text calls for a global function Process, this is the purpose C++ designers intended for static member functions. Use of a static member function has all the advantages of global functions without the chance for accessibility. B) Implementations C) Design and implement an iterator class for the tree class. There are details to be decided.