CarrCh06v1

CarrCh06v1 - C H A P T E R 6 List Implementations That Link...

Info iconThis preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: C H A P T E R 6 List Implementations That Link Data C ONTENTS Linked Data Forming a Chain Forming Another Chain Forming Yet Another Chain The Class Node A Linked Implementation of the ADT List Adding to the End of the List Adding at a Given Position Within the List The Private Method getNodeAt The Method remove The Method replace The Method getEntry The Method contains The Remaining Methods Using a Class Node That Has Set and Get Methods Tail References A Revised Implementation of the List The Pros and Cons of Using a Chain to Implement the ADT List Java Class Library: The Class LinkedList P REREQUISITES Chapter Chapter 4 5 Lists List Implementations That Use Arrays O BJECTIVES After studying this chapter, you should be able to G G Implement the ADT list by using a linked organization of data Discuss the advantages and disadvantages of a linked implementation of the list 113 114 CHAPTER 6 List Implementations That Link Data Using an array to implement the ADT list has both advantages and disadvantages, as you saw in Chapter 5. An array can either have a fixed size or be moved to a larger array. A fixed-size array can lead to a full list. Although dynamic array expansion can provide as much space as the list needs, you must move data each time you expand the array. In addition, any array requires you to move data either to make room for a new entry or to close up a gap after a deletion. This chapter uses a data organization that avoids moving data. As a result, adding or removing list entries take less time than these operations would with an array. As you will see, however, this new implementation has its own drawbacks. Linked Data 6.1 A chain of five desks 10 10 4 4 20 15 20 22 22 15 Figure 6-1 In Chapter 5, we used the analogy of a classroom to describe how data is stored in an array. Here we use a classroom to show you another way to organize data. Imagine an empty classroom—room L—that is assigned to a course. All available desks are in the hallway. Any student that registers for the course receives a desk, takes it into the room, and sits at it. Assume that the room can accommodate all of the desks in the hall. Each desk in the hallway has a number stamped on its back. This number—called an address—never changes and is not considered when desks are given to students. Thus, the room will eventually contain desks whose addresses are not sequential in general. Now imagine that Jill is among 30 students who are seated in room L at exactly 30 desks. Taped to each desktop is a blank piece of paper. As Jill entered the room, the instructor wrote on her paper the desk number (address) of another desk in the room. For example, the paper on Jill’s desk might contain the number 20. If her desk is desk 15, we say that desk 15 references desk 20 and that desks 15 and 20 are linked. Since the desks are linked to one another, we say that they form a chain of desks. Figure 6-1 shows a chain of five desks. No desk references the first desk in the chain, but the instructor remembers its desk number, 22. Notice that the last desk in the chain does not reference another desk; the piece of paper on this desk is blank. Linked Data 6.2 115 The chain of desks provides an order for the desks. Suppose that first in the chain is the student who arrived most recently. Written on this student’s desk is the desk number of the student who arrived just before. With one exception, everyone’s desk references the desk of the student who arrived just before. The exception is the person who arrived first. That person sits at the last desk, which does not reference another desk. The instructor knows the address of the first desk in the chain and so can ask questions of the student at that first desk. Then by looking at the address, or desk number, that is written on the paper on first desk, the instructor can locate the second desk in the chain and can question its occupant. Continuing in this way, the instructor can visit every desk in the order in which they appear in the chain. Ultimately, the instructor reaches the desk that references no other desk. The only way the instructor can locate the student in this last desk is to begin at the first desk. The instructor can traverse this chain in only one order. In Chapter 5, however, the instructor in room A was able to ask questions of any student in any order. Forming a Chain 6.3 Figure 6-2 How did the instructor form the chain of desks in the first place? Let’s return to the time when room L was empty and all available desks were in the hallway. Suppose that you arrive first. You get a desk from the hallway and enter the room. The instructor notes your desk’s number (address) and leaves the paper on your desk blank to indicate that no other student has arrived. The room appears as in Figure 6-2. One desk in the room 10 10 6.4 When the second student arrives, the instructor writes your desk’s number on the new desk’s paper and then remembers the number of the new (second) desk. Let’s assume that the instructor can remember only one desk number at a time. The room appears as in Figure 6-3. 6.5 When the third student arrives, the instructor writes the memorized desk number, which is that of the second desk, on the new desk’s paper and then remembers the number of the new (third) desk. The room appears as in Figure 6-4. 6.6 After all the students have arrived, the instructor knows only the desk number of the student who arrived most recently. On that student’s desk is the desk number of the student who arrived just previously. In general, written on each student’s desk is the number of the desk that belongs to the previous student that arrived. Since you were the first student to arrive, the paper on your desk is still blank. In Figures 6-1 through 6-4, you are the person at desk 10. 116 CHAPTER 6 List Implementations That Link Data Figure 6-3 Two linked desks, with the newest desk first 10 4 10 4 Three linked desks, with the newest desk first 10 4 10 20 20 4 Figure 6-4 Question 1 a. b. The instructor knows the address of only one desk. Where in the chain is that desk: first, last, or somewhere else? Who is sitting at that desk: the student who arrived first, the student who arrived last, or someone else? Question 2 Where in the chain of desks is a new desk added: at the beginning, at the end, or somewhere else? 6.7 The following pseudocode details the steps that the instructor took to form a chain of students in the order of their arrival: represents the new student’s desk New student sits at newDesk Instructor memorizes address of newDesk newDesk Linked Data while (students { } 117 arrive) newDesk represents the new student’s desk New student sits at newDesk Instructor writes memorized address on newDesk Instructor memorizes address of newDesk Notice that the chain of desks organizes the students in reverse order of their arrival times. The instructor places each new desk at the beginning of the chain. When done, the instructor knows the address of the first desk in the chain; seated at this desk is the last student to arrive. Forming Another Chain 6.8 Two linked desks, with the newest desk last 10 10 4 Figure 6-5 Suppose that the instructor wants to organize the chain in a different way, so that the first student to arrive will be first in the chain and the last student to arrive will be last. Once again, suppose that you are the first student to arrive. You are accommodated as you were in the previous example. That is, the instructor notes your desk’s number (address) and leaves the paper on your desk blank. So far, the situation is the same as the one shown in Figure 6-2. When the second student arrives, the instructor writes the number of the second desk on the paper that is on your desk and leaves the new desk’s paper blank. (Recall that in the previous chain your paper remained blank the entire time.) Note that the instructor remembers only the address of your desk, since you were the first student to arrive. Figure 6-5 shows the chain at this point. 4 When the third student arrives, the instructor needs to write the number of the third desk on the paper that is on the second desk. The instructor no longer knows the address of the second desk but can determine it by looking at the paper on your desk. Again, the instructor leaves the new desk’s paper blank, as you can see in Figure 6-6. 118 CHAPTER 6 List Implementations That Link Data Figure 6-6 Three linked desks, with the newest desk last 10 4 20 4 10 6.9 For each new student, the instructor must write the number of the new desk on the paper that is on the last desk in the chain. To locate the last desk, the instructor begins with the first desk, determines the address of the second desk, then determines the address of the third desk, and so on until the address of the last desk is known. That is, the instructor has to traverse the chain of desks each time to be able to link a new desk to the end of the chain of desks. After all of the students have arrived, the instructor still remembers your desk number, since you were the first student to arrive. Figure 6-7 shows the room after five students have arrived. Five linked desks, with the newest desk last 10 4 20 15 22 15 20 10 4 Figure 6-7 20 22 Written on the paper that is on your desk is the desk number for the second student to arrive. That is, your desk—which is first in the chain—references the desk of the second student to arrive. The second desk references the desk of the third student to arrive. Except for the last desk, Linked Data 119 each desk references the desk that belongs to the student who arrived next. The paper on the last student’s desk is blank. Question 3 a. b. The instructor remembers the address of only one desk. Where in the chain is that desk: first, last, or somewhere else? Who is sitting at that desk: the student who arrived first, the student who arrived last, or someone else? Question 4 Where in the chain of desks is a new desk added: at the beginning, at the end, or somewhere else? 6.10 The following pseudocode details the steps that the instructor took to add a desk to the end of the chain: represents the new student’s desk New student sits at newDesk Instructor memorizes address of newDesk while (students arrive) newDesk { } newDesk represents the new student’s desk New student sits at newDesk Instructor traverses the chain to locate its last desk Instructor writes address of newDesk on this last desk You can see that the instructor performed more work this time: Each new desk is added to the end of the chain, which requires the instructor to traverse the entire chain to locate the last desk. It is much easier to add a desk to the beginning of the chain than to its end. Notice that the chain of desks organizes the students in order of their arrival times. When done, the instructor knows the address of the first desk on the chain; seated at this desk is the first student to arrive. Question 5 What is an advantage of using a linked organization of data? What is a disadvantage? Forming Yet Another Chain 6.11 Organizing students chronologically by their arrival times required the instructor to add each new student to the beginning or end of the chain of desks. No student or desk currently in the room had to move. Suppose that the instructor instead wants to organize the students alphabetically by their names. We can examine the details of the necessary algorithm by considering room L after a few students enter the room and the instructor has organized them alphabetically. Assume that the students’ desks are linked, that the instructor knows the address of the first desk in the chain, and that this desk is occupied by the student whose name is alphabetically earliest. 6.12 Adding to a particular place in a chain. Imagine that a new student wants to join the students in room L. The student gets any available desk from the hallway. The instructor needs to link this new 120 CHAPTER 6 List Implementations That Link Data desk to the desks currently in the room so that the new student is in the correct position within the current arrangement of students. This link can be made without moving any current student in the room. To simplify our discussion, when we mention an address on a desk, we mean the address that is written on the paper that is on the desk. In contrast, a desk’s address is the fixed address that is stamped on the back of each desk. The details of how to insert a new desk into the chain depend on where in the chain the desk belongs. Consider the following cases: G G G Case 1: The new desk belongs before all current desks. Case 2: The new desk belongs between two current desks. Case 3: The new desk belongs after all current desks. As you will see, the last case is really not a special case. 6.13 Case 1. Figure 6-8 depicts Case 1 before we add the new desk to the beginning of the chain. In this figure, represents the new student’s desk represents the first desk in the chain G firstDesk A chain of desks just prior to adding a new desk to the beginning of the chain 5 8 20 20 5 Figure 6-8 newDesk G 14 firstDesk newDesk Recall that the instructor, or head of the chain, knows the address of the first desk. Two steps are necessary in this case: 1. Place the address of firstDesk on newDesk. (newDesk now references firstDesk.) 2. Give the address of newDesk to the instructor (head). Figure 6-9 illustrates the result of these steps. Notice that this case is like the situation described in Segment 6.4. Linked Data Figure 6-9 121 The addition of a new desk to the beginning of a chain of desks 8 5 5 20 14 firstDesk 20 14 newDesk 6.14 Case 2. Figure 6-10 shows Case 2 before we add the new desk between two desks currently in the chain, where G G G deskBefore Two consecutive desks within a chain of desks just prior to adding a new desk between them 12 5 8 newDesk 20 deskAfter 5 Figure 6-10 represents the new student’s desk represents the desk that will be before newDesk in the final arrangement deskAfter represents the desk currently after deskBefore; its address is on deskBefore newDesk deskBefore The following steps are necessary to place the new desk between the two consecutive desks deskand deskAfter: Before 1. Copy the address on deskBefore (that is, deskAfter’s address) and place it on newDesk. 2. Place the address of newDesk on deskBefore. 122 CHAPTER 6 List Implementations That Link Data Figure 6-11 illustrates the result of these steps. The addition of a new desk between two other desks 12 5 8 5 newDesk 20 deskAfter 12 Figure 6-11 deskBefore 6.15 Case 3. When the new desk belongs after all current desks, the situation is like Case 2, except that deskAfter does not exist. Remember that the paper on the desk of the last student is blank. That desk is deskBefore and does not reference any other. In this situation, Step 1 of Case 2 copies blank from deskBefore to newDesk. This step, together with the other step of Case 2, is exactly what needs to happen so that newDesk is last in the arrangement of desks. Thus, we can handle Case 3 in the same way that we handle Case 2. Question 6 If a chain of desks organizes students alphabetically by name, how can you find the student whose name is alphabetically first? Alphabetically last? 6.16 Removing an item from a chain. Students who leave room L return their desks to the hall. Such desks can be reassigned to other students who enter either room L or other rooms that share this hallway. Suppose that you leave room L because you want to drop the course. If you simply move your desk to the hallway, you will not actually remove yourself from the chain of desks in the room: Either another desk or the instructor will still reference your desk. You need to disconnect your desk from the chain, but the details of how you do this depend on where it is in the chain. Here are the possible cases: G G G Case 1: The desk to be removed is first in the chain of desks. Case 2: The desk to be removed is between two current desks. Case 3: The desk to be removed is last in the chain of desks. Once again, you will see that the last case is not really a special case. 6.17 Case 1. Figure 6-12 illustrates Case 1 before we remove the first desk from the chain. The following steps are necessary to remove the first desk: 1. By asking the instructor, locate the first desk. 2. Give the address that is written on the first desk to the instructor. This is the address of the second desk in the chain. 3. Return the first desk to the hallway. Figure 6-13 shows the chain after the first two steps take place. Notice that the first desk is no longer a part of the chain. Technically, it still references the second desk. But if this desk is ever used again, a new address will be written on its paper. Linked Data Figure 6-12 123 A chain of desks just prior to removing its first desk 20 12 14 20 14 Figure 6-13 A chain of desks just after removing its first desk 20 12 20 20 14 6.18 Case 2. Figure 6-14 shows Case 2 before we remove a desk that is between two current desks, where is the desk to be removed is the desk before the one to be removed deskAfter is the desk after the one to be removed G deskToRemove G deskBefore G A chain of desks just prior to removing a desk between two other desks 8 5 5 12 20 deskAfter 12 Figure 6-14 deskToRemove deskBefore 124 CHAPTER 6 List Implementations That Link Data Note that the address on deskBefore is the address of deskToRemove and the address on deskToReis that of the next desk, deskAfter. We’ll assume that we can determine the address of deskBefore, and from it determine the other addresses. The following steps are necessary to remove deskToRemove: move 1. Copy the address on deskToRemove to deskBefore. (deskBefore now references deskAfter, which is the desk currently after deskToRemove.) 2. Return deskToRemove to the hallway. Figure 6-15 shows the chain after these steps take place. Figure 6-15 A chain of desks just after removing a desk between two other desks 8 5 5 12 20 5 deskAfter deskToRemove deskBefore 6.19 Before and after removing the last desk from a chain Before After 8 8 5 5 8 Figure 6-16 Case 3. Finally, when the desk to be removed is last in the chain of desks, we can proceed as for Case 2. Remember that the paper on the last desk in the chain is blank because that desk does not reference any other desk. Therefore, Step 1 of Case 2 would copy a blank from the last desk to deskBefore , as 6-16 illustrates. This action is exactly what needs to happen so that deskBefore is last in the chain of desks. deskToRemove deskBefore deskToRemove deskBefore Question 7 What steps are necessary to remove the third desk in a chain of five desks? Question 8 What steps are necessary to remove the first desk in a chain of five desks? The Class Node 125 The Class Node 6.20 The previous section described how you can organize data without physically moving it. This section expresses these ideas in Java by implementing the ADT list. Recall that a list is a collection of objects that are organized by their positions in the collection. We begin by defining the Java equivalent of a desk, the node. Nodes are objects that you typically link together to form a data structure. Our particular nodes have two data fields each: one to reference an entry in the list and one to reference another node. An entry in the list is analogous to a person who sits at a desk. The reference to another node is analogous to the desk address written on the paper that is on each desk. 6.21 The class that represents these nodes can have the following form: class Node { private Object data; // entry in list private Node next; // link to next node < Constructors > . . . < Accessor and mutator methods: getData, setData, getNextNode, setNextNode > . . . } // end Node Let’s focus on the data fields. The field data is of type Object, so the entries in the list are objects. In this case, data contains a reference to the desired object. However, if the entries in the list had a primitive data type such as int, data would be of type int and would actually contain the list entry. Sometimes we will call this field the data portion of the node. The field next contains a reference to another node. Notice that its data type is Node, which is the class that we are currently defining! Such a circular definition might surprise you, but it is perfectly legal in Java. It also enables one node to reference another node, just as one desk referenced another desk in the first section of this chapter. Notice that a desk did not reference a student. Likewise, a node does not reference a list entry but rather references an entire node that contains a list entry. Sometimes we will call this field the link portion of the node. Figure 6-17 illustrates two nodes that are linked and contain either integers or references to objects. Figure 6-17 Two linked nodes with (a) primitive data; (b) object data (b) (a) 23 6.22 4 The rest of the class definition is uneventful. Constructors to initialize the node are useful, and since the data fields are private, methods to access and alter their contents are necessary. But are they really? If we intend Node to be for public use, like our other classes, such methods are necessary; however, Node is an implementation detail of the ADT list that should be hidden from the list’s client. 126 CHAPTER 6 List Implementations That Link Data One way to hide Node from the world is to define it within a package that also contains the class that implements the list. Another way—the way we will use here—is to define Node within the class that implements the list. Such a class is declared to be private and is called an inner class. The data fields of an inner class are accessible directly by the outer class without the need for accessor and mutator methods. Thus, we can simplify the definition of Node greatly: private class Node { private Object data; // data portion private Node next; // link to next node private Node(Object dataPortion) { data = dataPortion; next = null; } // end constructor private Node(Object dataPortion, Node nextNode) { data = dataPortion; next = nextNode; } // end constructor } // end Node We did not include a default constructor because we will not need one. In a moment, you will see how to use this class as an inner class. Later, in Segment 6.42, we will talk about a definition of Node that does have set and get methods. A Linked Implementation of the ADT List 6.23 In the example in the first section of this chapter, the instructor remembered the address of the first desk in a chain of desks. Similarly, the linked implementation for the ADT list uses a chain of nodes and must “remember” the address of the first node in this chain. The implementation does so by recording a reference to the first node in a data field called the head reference. Here is an outline of a class definition that implements the ADT list and contains the class Node as an inner class. Recall that Chapter 4 introduced the interface ListInterface. public class LList1 implements ListInterface { private Node firstNode; // reference to first node private int length; // number of entries in list public LList() { clear(); } // end default constructor public final void clear() { 1. We named this class LList instead of LinkedList to avoid confusion with Java’s class LinkedList in the package java.util. You will see Java’s LinkedList later in this chapter. A Linked Implementation of the ADT List 127 firstNode = null; length = 0; } // end clear < Implementations of the public methods add, remove, replace, getEntry, contains, getLength , isEmpty, isFull , and display go here. > . . . // ---------------private!----------------------------/** Task: Returns a reference to the node at a given position. * Precondition: List is not empty; 1 <= givenPosition <= length. */ private Node getNodeAt(int givenPosition) { } < Implementation deferred > // end getNodeAt private class Node // private inner class { < See Segment 6.22. > } // end Node } // end LList The data field firstNode is the head reference of the chain of nodes. Just like the instructor who knew the address of the first desk in the chain of desks, firstNode references the first node in the chain of nodes. Another data field, length, records the number of entries in the current list. This number is also the number of nodes in the chain. The default constructor simply initializes these data fields by calling clear. So initially, a list is empty, firstNode is null, and length is 0. As we mentioned in Chapter 2, when a constructor calls another public method such as clear, that method should be final so that no subclass can override it, thereby changing the effect of the constructor. Adding to the End of the List 6.24 We will now implement the public methods of the class LList, beginning with the first add method. This method adds a new entry to the end of the list. Recall from Segment 6.10 the steps that we took when the instructor added a new desk to the end of the chain. Initially when the first student arrived, we had the following steps: represents the new student’s desk New student sits at newDesk Instructor memorizes address of newDesk newDesk Here are the analogous steps that add must take to add the first entry to an initially empty list. Note that a new desk is analogous to a new node and the instructor is analogous to firstNode. references a new instance of Node Place data in newNode firstNode = address of newNode newNode list: In Java, these steps appear as follows, where newEntry references the entry to be added to the Node newNode = new Node(newEntry); firstNode = newNode; 128 CHAPTER 6 List Implementations That Link Data Figure 6-18 illustrates these steps. Notice that in Part b of this figure, both firstNode and newNode reference the same node. After the insertion of the new node is complete, only firstNode should reference it. We could set newNode to null, but as you will see shortly, newNode is a local variable of the method add. As such, newNode will not exist after add ends its execution. The same is true of the local variable newEntry. Figure 6-18 (a) An empty list and a new node; (b) after adding a new node to a list that was empty (a) firstNode firstNode newNode newNode newEntry 6.25 (b) newEntry Now, to add a desk to the end of the chain, the instructor took the following steps: newDesk represents the new student’s desk New student sits at newDesk Instructor locates the last desk in the chain Instructor writes address of newDesk on this last desk The analogous steps that add must take to add a new entry to the end of a list are references a new instance of Node Place data in newNode Locate last node in chain Place address of newNode in this last node newNode That is, we make the last node in the chain reference the new node. In Java, these steps appear as follows: Node newNode = new Node(newEntry); Node lastNode = getNodeAt(length); // get reference to last node lastNode.next = newNode; // make last node reference new node The method getNodeAt is a private method of the class LList that locates and returns a reference to the node at a given position within the list. Its specifications are given near the end of Segment 6.23. Notice that lastNode is an instance of Node, so lastNode.next is that node’s data field next. Figure 6-19 illustrates this addition to the end of a chain of nodes. To simplify the figure, we have omitted the actual entries in the list. These entries are objects that the nodes reference. As in Figure 6-18, we are left with external references into the chain of nodes that we do not want. Again, since these reference variables newNode and lastNode will be local to the method, they will not exist for long. If they did persist, we would set these variables to null after the addition was complete. A Linked Implementation of the ADT List Figure 6-19 129 A chain of nodes (a) just prior to adding a node at the end; (b) just after adding a node at the end (a) firstNode lastNode newNode lastNode newNode (b) firstNode 6.26 The Java method. Assuming that we have the private method method add based on the previous thoughts: getNodeAt, we can complete the public boolean add(Object newEntry) { Node newNode = new Node(newEntry); if (isEmpty()) firstNode = newNode; else // add to end of nonempty list { Node lastNode = getNodeAt(length); lastNode.next = newNode; // make last node reference new node } // end if length++; return true; } // end add This method first creates a new node for the new entry. If the list is empty, it adds the new node by making firstNode reference it. If the list is not empty, however, we must locate the end of the list. Since we have a reference only to the first node, we must traverse the list until we locate the last node and obtain a reference to it. We will define a private method getNodeAt to accomplish this task. Since the data field length contains the size of the list, and since we identify list entries by their positions within the list beginning with 1, the last node is at position length. We need to pass this value to getNodeAt. Once getNodeAt gives us a reference to the last node, we can set the last node’s link to reference the new node. The method getNodeAt does the messy work; we will examine its implementation later, in Segment 6.33. In the meantime, we can implement the public methods knowing only what getNodeAt does, and not how it does it. 130 CHAPTER 6 List Implementations That Link Data Adding at a Given Position Within the List 6.27 The second add method adds a new entry at a specified position within the list. After creating a new node that newNode references, we determine whether the existing list is empty. If it is, we add the new node to the list by writing firstNode = newNode, as we did in the first add method. If the list is not empty, we must consider two cases: Case 1: Adding the entry to the beginning of the list Case 2: Adding the entry at a position other than the beginning of the list G G 6.28 Case 1. In the context of desks in a room, the necessary steps for the first case are newDesk represents the new student’s desk New student sits at newDesk Place the address of the first desk on newDesk (the instructor knows the address of the first desk) Give the address of newDesk to the instructor As a result of these steps, the new desk references the current first desk in the chain and becomes the new first desk. Here are the analogous steps that add must take to add to the beginning of a list: references a new instance of Node Place data in newNode Set newNode’s link to firstNode Set firstNode to newNode newNode That is, we make the new node reference the first node in the chain, making it the new first node. Figure 6-20 illustrates these steps, and the following Java statements implement them: Node newNode = new Node(newEntry); newNode.next = firstNode; firstNode = newNode; Figure 6-20 A chain of nodes (a) just prior to adding a node at the beginning; (b) just after adding a node at the beginning (a) (b) firstNode firstNode newNode newNode Adding a node to an empty chain, as Figure 6-18 depicts, is actually the same as adding a node to the beginning of a chain. Question 9 The code that we developed in Segment 6.24 to add a node to an empty chain is Node newNode = new Node(newEntry); firstNode = newNode; A Linked Implementation of the ADT List 131 The code that we just developed to add to the beginning of a chain is Node newNode = new Node(newEntry); newNode.next = firstNode; firstNode = newNode; Why do these statements work correctly when the chain is empty? 6.29 Case 2. In the second case, we add an entry to the list at a position other than the beginning. The necessary steps for the second case in the context of desks in a room are newDesk references the new student’s desk deskBefore represents the desk that will be before the new desk deskAfter represents the desk after deskBefore; its address is on deskBefore Place the address of deskAfter on newDesk Place the address of newDesk on deskBefore Here, the analogous steps that add must take are newNode references the new node nodeBefore references the node that will be before the new node Set nodeAfter to nodeBefore’s link Set newNode’s link to nodeAfter Set nodeBefore’s link to newNode The following Java statements implement these steps: Node newNode = new Node(newEntry); Node nodeBefore = getNodeAt(newPosition-1); Node nodeAfter = nodeBefore.next; newNode.next = nodeAfter; nodeBefore.next = newNode; Figure 6-21 Figure 6-21a shows the chain after the first three statements execute, and Figure 6-21b shows it after the node has been added. A chain of nodes (a) just prior to adding a node between two adjacent nodes; (b) just after adding a node between two adjacent nodes (a) firstNode nodeBefore nodeAfter newNode (b) firstNode nodeBefore newNode nodeAfter 132 CHAPTER 6 List Implementations That Link Data 6.30 The Java method. The following implementation of the add method summarizes these ideas: public boolean add(int newPosition, Object newEntry) { boolean isSuccessful = true; if ((newPosition >= 1) && (newPosition <= length+1)) { Node newNode = new Node(newEntry); if (isEmpty() || (newPosition == 1)) // case 1 { newNode.next = firstNode; firstNode = newNode; } else // case 2: newPosition > 1, list is not empty { Node nodeBefore = getNodeAt(newPosition-1); Node nodeAfter = nodeBefore.next; newNode.next = nodeAfter; nodeBefore.next = newNode; } // end if length++; } else isSuccessful = false; return isSuccessful; } // end add 6.31 An out-of-memory error. One of the list implementations given in Chapter 5 used a fixed-size array to represent the list entries. You saw that the array—and therefore the list—could become full. With a linked implementation, the list cannot become full. Anytime you add a new entry, you create a new node for that entry. It is possible, however, for your program to use all of your computer’s memory. If this occurs, your request for a new node will cause the error OutOfMemoryError. You could interpret this condition as a full list; however, an OutOfMemoryError is fatal, and the client will not have the opportunity to react to it. The Private Method getNodeAt 6.32 To complete our implementation of the add methods, we need to implement the method getNodeAt, which returns a reference to the node at a given position within the list. Since the method returns a reference to a node, the method is an implementation detail that we would not want a client to use. Thus, getNodeAt should be a private method. Recall the specifications for this method: /** Task: Returns a reference to the node at a given position. * Precondition: List is not empty; 1 <= givenPosition <= length. */ private Node getNodeAt(int givenPosition) Segments 6.2 and 6.9 discussed how an instructor locates a particular desk within a chain of desks by beginning at the head of the chain and traversing it from one desk to another. The technique is the same here. The data field firstNode contains a reference to the first node in the list. That node contains a reference to the second node in the list, and so on. A Linked Implementation of the ADT List 133 We can use a temporary variable currentNode to reference the nodes, one at a time, as we traverse the chain from the first node to the desired node. Initially, we set currentNode to firstNode so that it references the first node in the chain. If we are seeking the first node, we are done. Otherwise, we move to the next node by executing currentNode = currentNode.next; If we are seeking the second node, we are done. Otherwise, we move to the next node by executing currentNode = currentNode.next; once again. We continue in this manner until we locate the node at the desired position within the list. 6.33 The implementation for getNodeAt follows: /** Task: Returns a reference to the node at a given position. * Precondition: List is not empty; 1 <= givenPosition <= length. */ private Node getNodeAt(int givenPosition) { Node currentNode = firstNode; // traverse the list to locate the desired node for (int counter = 1; counter < givenPosition; counter++) currentNode = currentNode.next; return currentNode; } // end getNodeAt Within the for loop, currentNode should never become null, if the method’s precondition is met. Thus, currentNode.next never executes if currentNode is null. Notice that the previous add methods enforce this method’s precondition. Programming Tip: If ref is a reference to a node in a chain, before you use it to access ref.data or ref.next, be sure that ref is not null. The Method remove 6.34 The remove method removes the entry at a specified position within a nonempty list. We must consider two cases: G G 6.35 Case 1: Removing the entry at the beginning of the list Case 2: Removing an entry at a position other than the beginning of the list Case 1. Segment 6.17 discussed the first case in the context of desks in a room. The necessary steps then were By asking the instructor, locate the first desk. Give the address that is written on the first desk to the instructor. This is the address of the second desk in the chain. Return the first desk to the hallway. Here the analogous steps are Set firstNode to the link in the first node. Since references to the first node no longer exist, the system automatically recycles its memory. 134 CHAPTER 6 List Implementations That Link Data Figure 6-22 illustrates these steps, and the following Java statement implements them: firstNode = firstNode.next; Figure 6-22 A chain of nodes (a) just prior to removing the first node; (b) just after removing the first node (a) firstNode (b) firstNode 6.36 Case 2. In the second case, we remove an entry at a position other than the beginning of the list. Segment 6.18 discussed this case in the context of desks in a room. The necessary steps then were Locate the desk before deskToRemove; call it deskBefore. The address on deskBefore is the address of deskToRemove. The address on deskToRemove is that of the next desk, deskAfter, in the chain. Copy the address on deskToRemove to deskBefore. (deskBefore now references deskAfter, which is the desk currently after deskToRemove.) Return deskToRemove to the hallway. Here the analogous steps are Let nodeBefore reference the node before the one to be removed. Set nodeToRemove to nodeBefore’s link; nodeToRemove now references the node to be removed. Set nodeAfter to nodeToRemove’s link; nodeAfter now references the node after the one to be removed. Set nodeBefore’s link to nodeAfter. (nodeToRemove is now disconnected from the chain.) Set nodeToRemove to null. Since references to the disconnected node no longer exist, the system automatically recycles its memory. The following Java statement implements these steps: Node nodeBefore = getNodeAt(givenPosition-1); Node nodeToRemove = nodeBefore.next; Node nodeAfter = nodeToRemove.next; nodeBefore.next = nodeAfter; nodeToRemove = null; Figure 6-23a illustrates the chain after the first three statements execute, and Figure 6-23b shows it after the node is removed. A Linked Implementation of the ADT List Figure 6-23 135 A chain of nodes (a) just prior to removing an interior node; (b) just after removing an interior node (a) nodeBefore nodeToRemove nodeAfter nodeBefore nodeToRemove nodeAfter (b) 6.37 The Java method. The remove method has the following implementation. Recall that the method returns the entry that it deletes from the list. Notice that we use the private method getNodeAt, which we wrote originally for the add methods. Also notice that we do not set nodeToRemove to null after disconnecting the node. As we have mentioned before, nodeToRemove is local to the remove method and does not exist after the method completes executing. So although we could set nodeToRemove to null, doing so is not necessary. public Object remove(int givenPosition) { Object result = null; // return value if (!isEmpty() && (givenPosition >= 1) && (givenPosition <= length)) { if (givenPosition == 1) // case 1: remove first entry { result = firstNode.data; // save entry to be removed firstNode = firstNode.next; } else // case 2: givenPosition > 1 { Node nodeBefore = getNodeAt(givenPosition-1); Node nodeToRemove = nodeBefore.next; Node nodeAfter = nodeToRemove.next; nodeBefore.next = nodeAfter; // disconnect the node to be removed result = nodeToRemove.data; // save entry to be removed } // end if length--; } // end if 136 CHAPTER 6 List Implementations That Link Data return result; } // end remove // return removed entry, or null // if operation fails Note: Allocating and deallocating memory When you use the new operator, you create, or instantiate, an object. At that time, the Java runtime environment allocates, or assigns, memory to the object. When you create a node for a linked chain, we sometimes say that you have allocated the node. After the method remove removes a node from a chain, you have no way to reference it, so you cannot use it. As Segment 1.19 noted, the Java run-time environment automatically deallocates and recycles the memory associated with such nodes without explicit instruction from the programmer. The Method replace 6.38 Replacing, or revising, a list entry requires us to simply replace the data portion of a node with other data. The implementation appears as follows: public boolean replace(int givenPosition, Object newEntry) { boolean isSuccessful = true; if (!isEmpty() && (givenPosition >= 1) && (givenPosition <= length)) { Node desiredNode = getNodeAt(givenPosition); desiredNode.data = newEntry; } else isSuccessful = false; return isSuccessful; } // end replace Question 10 Compare the effort involved in replacing an entry in a list using the previous method replace and the method replace given in Segment 5.9 of Chapter 5. The Method getEntry 6.39 Retrieving a list entry is also straightforward: public Object getEntry(int givenPosition) { Object result = null; // result to return if (!isEmpty() && (givenPosition >= 1) && (givenPosition <= length)) result = getNodeAt(givenPosition).data; return result; } // end getEntry The method getNodeAt returns a reference to the desired node, so getNodeAt(givenPosition).data is the data portion of that node. A Linked Implementation of the ADT List 137 Although our implementations of getEntry and replace are easy to write, each does more work than if we had used an array to represent the list. Here, getNodeAt starts at the first node in the chain and moves from node to node until it reaches the desired one. In Segment 5.9 of Chapter 5, you saw that replace and getEntry can reference the desired array element directly without involving any other array element. The Method contains 6.40 To determine whether a list contains a given entry, you must look at the entries in the list, one at a time. In Chapter 5, where we used an array to represent the list’s entries, we examined each array element—starting at index zero—until we either found the desired entry or discovered that it was not in the array. We use the same general approach here to search a chain for a particular piece of data. We begin at the first node, and if that does not contain the entry we are seeking, we look at the second node, and so on. When searching an array, we use an index. To search a chain, we use a reference to a node. For example, currentNode could reference the node that we want to examine. Initially, we want currentNode to reference the first node in the chain, so we set it to firstNode. To make currentNode reference the next node, we would execute the statement currentNode = currentNode.next; Thus, the method contains has the following implementation: public boolean contains(Object anEntry) { boolean found = false; Node currentNode = firstNode; while (!found && (currentNode != null)) { if (anEntry.equals(currentNode.data)) found = true; else currentNode = currentNode.next; } // end while return found; } // end contains The method has the same general form as the corresponding method in Segment 5.10 of Chapter 5. The Remaining Methods 6.41 The method isFull should always return false. The only time a list whose implementation is linked could appear full is when the system cannot provide memory to the add method for a new node. In that case, an OutOfMemoryError occurs, which is fatal. A client would not have the opportunity to call isFull. The implementations of the methods getLength and isEmpty are the same as for the arraybased implementation that you saw in Chapter 5. We leave the implementation of display as an exercise. 138 CHAPTER 6 List Implementations That Link Data Question 11 Write an implementation for the method display that displays the entries in the list, one per line. Assume that the objects in the list belong to a class that implements the method toString. You should look at the previous implementation of the method contains for an idea of how to construct the necessary loop. Here, however, the loop does not end until the entire chain is traversed. Question 12 The implementation of isEmpty compares the data field What is another equally brief implementation of this method? length with zero. Using a Class Node That Has Set and Get Methods 6.42 Since Node is an inner class, the class LList can access Node’s private data fields directly by name. Doing so makes the implementation somewhat easier to write, read, and understand, particularly for novice Java programmers. However, some computer scientists feel that you should access a class’s data fields only by calling accessor and mutator (set and get) methods. Suppose that we had included the methods getData, setData, getNextNode, and setNextNode in the class Node, as described earlier in Segment 6.21. We could then revise the implementation of LList by making changes such as the following: G G G Change currentNode.data to currentNode.getData() Change currentNode.next to currentNode.getNextNode() Change desiredNode.data = newEntry; to desiredNode.setData(newEntry); G Change nodeBefore.next = nodeAfter; to nodeBefore.setNextNode(nodeAfter); If we had written Node and LList in this way, Node could be either a private inner class or a public class. Here Node is an implementation detail that we want to hide, so making it an inner class is appropriate. But if we ever changed our mind and wanted to use Node as a public class, we could do so without revising it and LList. Question 13 What changes to the class instances of the class? LList are necessary to enable a client to serialize Tail References 6.43 The problem. Imagine that we have a collection of data from which we will create a list. That is, our data will be the list’s entries. If the data is in the order in which the entries will appear in the list, we create the list by repeatedly adding the next entry to the end of the list. We could do this by using the implementation of the add method that we presented in Segment 6.26. However, if you examine that method, you will discover that it invokes the private method getNodeAt to locate the last node in the chain and hence the last entry in the list. To accomplish this task, getNodeAt must begin at the first entry and traverse the chain until it locates the last node. Tail References 139 Given a reference to the last node, add can insert the new entry at the end of the list. This reference, however, is not retained when the method completes its task. Thus, if we add another entry to the end of the list, add will invoke getNodeAt again to traverse the list from its beginning. Since we plan to add entries repeatedly to the end of the list, many repetitious traversals will occur. 6.44 Figure 6-24 A solution. In such cases, maintaining a reference to the end of the chain—as well as a reference to the beginning of the chain—is advantageous. We call such a reference to the end of a chain a tail reference. Figure 6-24 illustrates a linked chain with both head and tail references. A linked chain with a head reference and a tail reference firstNode lastNode The tail reference, like the head reference, is a private data field of the class. The private data fields in our revised implementation then would be private Node firstNode; // head reference to first node private Node lastNode; // tail reference to last node private int length; // number of entries in list Question 14 Examine the implementation of the class LList given in the previous section. Which methods would require a new implementation if you used both a head reference and a tail reference? A Revised Implementation of the List By examining the class LList, you should find that the constructor, both add methods, and the method are the ones that might alter the head and tail references, and thus those methods will need to be revised. The rest of the original implementation remains the same. Let’s examine these revisions. remove 6.45 The constructor. The default constructor initializes both the head and tail references: public LList() { firstNode = null; lastNode = null; null length = 0; } // end default constructor Here, and in the rest of the revision, changes from the original implementation appear in color. 6.46 Adding to the end of the list. Adding to the end of an empty list requires both the head and tail references to reference the new solitary node. Thus, after creating a new node that newNode references, the add method would execute firstNode = newNode; lastNode = newNode; 140 CHAPTER 6 List Implementations That Link Data Adding to the end of a nonempty list no longer requires a traversal to locate the last entry: The tail reference lastNode provides this information. After making the addition, the tail reference must change to refer to the new last entry. The following statements perform these steps, as Figure 6-25 illustrates: lastNode.next = newNode; lastNode = newNode; Figure 6-25 Adding a node to the end of a nonempty chain that has a tail reference (a) After executing lastNode.next = newNode; lastNode newNode (b) After executing lastNode = newNode; lastNode newNode The first add method reflects the previous comments: public boolean add(Object newEntry) { Node newNode = new Node(newEntry); if (isEmpty()) firstNode = newNode; else lastNode.next = newNode; lastNode = newNode; length++; return true; } // end add 6.47 Adding to the list at a given position. Adding to a list by position affects the tail reference only when we are adding to an empty list or adding to the end of a nonempty list. Other cases do not affect the tail reference, so we treat them as we did in Segment 6.30 when we did not have a tail reference. Tail References 141 Thus, the implementation of the method that adds by position is public boolean add(int newPosition, Object newEntry) { boolean isSuccessful = true; if ((newPosition >= 1) && (newPosition <= length+1)) { Node newNode = new Node(newEntry); if (isEmpty()) { firstNode = newNode; lastNode = newNode; } else if (newPosition == 1) { newNode.next = firstNode; firstNode = newNode; } else if (newPosition == length + 1) { lastNode.next = newNode; lastNode = newNode; } // end if else { Node nodeBefore = getNodeAt(newPosition-1); Node nodeAfter = nodeBefore.next; newNode.next = nodeAfter; nodeBefore.next = newNode; } // end if length++; } else isSuccessful = false; return isSuccessful; } // end add 6.48 Removing an entry from a list. Removing an entry can affect the tail reference in two cases: G G Case 1: If the list contains one entry and we remove it, an empty list results and we must set both the head and tail references to null. Case 2: If the list contains several entries and we remove the last one, we must change the tail reference so that it references the new last entry. Figure 6-26 illustrates these two cases, and the following method implements them: 142 CHAPTER 6 List Implementations That Link Data Figure 6-26 Removing a node from a chain that has both head and tail references when the chain contains (a) one node; (b) more than one node (a) Before firstNode After lastNode firstNode lastNode (b) Before firstNode lastNode After firstNode lastNode public Object remove(int givenPosition) { Object result = null; // return value if (!isEmpty() && (givenPosition >= 1) && (givenPosition <= length)) { if (givenPosition == 1) { result = firstNode.data; firstNode = firstNode.next; if (length == 1) lastNode = null; // solitary entry was removed null } else { Node nodeBefore = getNodeAt(givenPosition-1); Node nodeToRemove = nodeBefore.next; Node nodeAfter = nodeToRemove.next; nodeBefore.next = nodeAfter; // disconnect node to be removed result = nodeToRemove.data; // save entry to be removed The Pros and Cons of Using a Chain to Implement the ADT List 143 if (givenPosition == length) lastNode = nodeBefore; // last node was removed } // end if length--; } // end if return result; } // end remove The Pros and Cons of Using a Chain to Implement the ADT List 6.49 This chapter has showed you how to use a chain in the implementation of the ADT list. One of the greatest advantages of this approach is that the chain, and therefore the list, can grow as large as necessary. As long as memory is available, you can add as many nodes to a chain as you wish. Although you can also use dynamic array expansion—as Chapter 5 describes—to allow the list to grow, each time a larger array is necessary, you must copy the entries from the full array to the new array. No such copying is required when you use a chain. In addition, a chain allows you to add and remove nodes without moving any existing entries that are already in the list. With an array, adding and removing entries usually requires that other entries be moved within the array. However, you must traverse a chain from its beginning to determine where to make the addition or deletion. Retrieving an existing entry from a chain requires a similar traversal to locate the desired entry. When you use an array instead of a chain, you can access any element directly by position, without searching the array. Lastly, a chain requires more memory than an array. Although both data structures contain references to data objects, each node in a chain also contains a reference to another node. Java Class Library: The Class LinkedList 6.50 Recall from Chapter 4 that the standard Java package java.util contains the interface List. This interface is like our ListInterface, but some methods have different names and the list entries begin at position 0 instead of 1. This same package contains the class LinkedList. This class implements the interface List and contains additional methods that add, remove, and retrieve entries at the beginning or end of a list. These additional methods are as follows: public void addFirst(Object newEntry) Adds newEntry to the beginning of the list. public void addLast(Object newEntry) Adds newEntry to the end of the list. public Object removeFirst() Removes and returns the first entry in the list. public Object removeLast() Removes and returns the last entry in the list. public Object getFirst() Returns the first entry in the list. 144 CHAPTER 6 List Implementations That Link Data public Object getLast() Returns the last entry in the list. Descriptions of other methods in this class are available at http://java.sun.com/products/jdk/1.4/docs/api/index.html C HAPTER S UMMARY G You can form a chain of linked data by using objects called nodes. Each node has two parts. One part contains either data of a primitive type or a reference to a data object. A second part references the next node in the chain. The last node, however, references no other node and contains null. A head reference external to the chain references the first node. G You can add or remove any node that is linked to other nodes in a chain by changing at most two references. G Adding or removing a node that is linked to other nodes in a chain must consider a special case: when the node will be or is first in the chain. G Locating a particular node in a chain of linked nodes requires a traversal of the chain. Beginning at the first node, you move from node to node sequentially until you reach the desired node. G Adding or removing a node that is last in a chain of linked nodes requires a traversal of the entire chain. Maintaining a tail reference to the last node eliminates the need for a traversal when adding a node at the end of the chain. A tail reference, however, makes removing the last node a bit more difficult. PROGRAMMING T IP G If ref is a reference to a node in a chain, be sure that ref is not null before you use it to access ref.data or ref.next. E XERCISES 1. Add a constructor to the class LList that creates a list from a given array of objects. Consider at least two different ways to implement such a constructor. Which way does the least amount of work? 2. Suppose that you want an operation for the ADT list that removes the first occurrence of a given object from the list. The signature of the method could be as follows: public boolean remove(Object anObject) The method returns true if the list contained anObject and the object was removed. Write an implementation of this method for the class LList. 3. Suppose that you want an operation for the ADT list that returns the position of a given object in the list. The signature of the method could be as follows: public int getPosition(Object anObject) Write an implementation of this method for the class LList. 4. Implement a replace method for the class LList that returns the replaced object. 5. Implement an equals method for the class LList that returns true when the entries in one list equal the entries in a second list. Java Class Library: The Class LinkedList 145 6. Suppose that a list contains Comparable objects. Implement the following methods for the class LList. a. The method getMin that returns the smallest object in the list. b. The method removeMin that removes and returns the smallest object in the list. PROJECTS 1. Revise the inner class Node so that it has the set and get methods mentioned in Segment 6.42. Then revise the class LList so that it invokes these set and get methods instead of accessing the private data fields data and next directly by name. 2. Create a Java interface that contains the six methods described in Segment 6.50 and the three methods described in Exercises 2, 3, and 4. Then extend this interface and ListInterface to form DoubleEndedListInterface. Write a class that implements DoubleEndedListInterface. Represent the list’s entries by using a chain of nodes that has both a head reference and a tail reference. Write a program that thoroughly tests your class. 3. Repeat Project 2, but do not use a tail reference. 4. Implement the ADT bag that Project 1 of Chapter 4 describes. Represent the bag as a chain of linked nodes. 5. A set is a special bag that does not allow duplicates. Project 1 of Chapter 4 describes the ADT bag. Specify the operations for the ADT set. Implement the set by using a chain of linked nodes. 6. Adding nodes to or removing nodes from a linked chain requires a special case when the operation is at the beginning of the chain. To eliminate the special case, you can add a dummy head node at the beginning of the chain. The dummy head node is always present but does not contain a list entry. The chain, then, is never empty, and so the head reference is never null, even when the list is empty. Modify the class LList, as presented in this chapter, by adding a dummy head node to the chain. ...
View Full Document

Ask a homework question - tutors are online