Fundamentals of Computer Science II (CSC-152 97F)

[News] [Basics] [Syllabus] [Outlines] [Assignments] [Examples] [Readings] [Bailey Docs]

Outline of Class 25: Singly-Linked Lists

Miscellaneous

• As you could probably tell, I was not very happy with your participation in Friday's class. So, I'm instituting a new policy. If you don't talk in class (asking and answering questions), I will simply end the class and assume that we've covered the topics.
• The readings on representation will be ready tomorrow. Sorry for the delay.
• Hopefully, I'll get grades back to you on Tuesday or Wednesday.
• Just in case you're wondering, the midterm exam will be open book, open notes, open web, open computers (although I won't ask you to write any real code). It will cover the introductory Java topics chapters 1 through 6 of Bailey, and the other topics we've discussed in class. You can ask me questions on Wednesday and Thursday evenings.
• Please finish reading chapter 6 of Bailey. You need not start on chapter 7.
• Sometime this week, I'll be asking you to take a survey on your use of the course web and on your Meyers-Briggs personality type (this is just a warning so that it won't be a surprise).

• As we discussed in the previous class session, singly linked lists are composed of nodes that are connected by directed links from node to node.
• Depending on implementation priorities, you may want to wrap a sequence of nodes in another structure. This makes it easier to keep track of things like the last element of the list.
• Head and Tail are basic list operations.
• Today, we'll consider other list operations.

Insertion

• One primitive operation on lists is insertion.
• As discussed earlier, there are a number of forms of insertionn in a list, including
• Insertion of a new element at the head
• Insertion of a new element at the tail
• Insertion of a new element at the current position
• If we are using the `List` class, we might write our insert at head function as follows
```/** Insert a non-null object at the head of the list.
* pre: The element is not null.
* pre: The current list has been initialized
* post: The head of the list is now that element.
* post: The tail of the list is the previous list.
*/
// Create a node to hold the new element.
// Is the list currently empty?  If so, just insert the element
// at front and back, and we're done.
if (front == null) {
}
// Otherwise, update the next reference of the new head
// and replace the front of the list
else {
// We don't need to modify the back of the list.
// Should we modify current?
}
```
• If we are not using a separate `List` structure, we can't really talk about modifying the current list. Hence, we need to create a static method that takes a `Node` as a parameter, and returns a (potentially new) `Node`.
```/**
* Construct a new list from a new head element and old tail.
*/
public Node cons(Element head, Node tail) {
} // cons
```
• Insertion at the end of the list (i.e., appending to a list) also depends on which representation of list you choose, and whether or not there is a `back` field.
• In the simplest `List`-based version (in which we do have a `back`.
```/**
* Append an element to the end of the list.
* pre: The element is not null.
* pre: The list has been appropriately initialized and maintained.
* post: The list now contains the element at the end.
*/
public void appendToEnd(Object element) {
// Create the node for the new element
Node new_end = new Node(element,null);
// Special case: the list is empty
if (front == null) {
front = new_end;
back = new_end;
current = new_end;
}
// Normal case: we just need to update the end
else {
back.setNext(new_end);
back = new_end;
}
} // appendToEnd
```
• We can make this more complicated by dropping the `back` field.
```public void appendToEnd(Object element) {
// Create the node for the new element
Node new_end = new Node(element,null);
// Special case: the list is empty
if (front == null) {
front = new_end;
}
// Normal case: need to traverse the list until we find
// the end
else {
Node traverse = front;
while (traverse.next != null) {
traverse = traverse.next;
}
traverse.next = new_end;
}
} // appendToEnd
```
• We would use a similar technique for our node-based version

Deletion

• The converse of insertion is deletion. We will frequently need to delete elements from our list.
• As with most list-based functions, there are a number of varieties of deletion.
• We might delete the head of a list.
• We might delete the last element of a list.
• We might delete the current element of a list.
• We might delete all instances of a particular object.
• We might delete the first instance of a particular object.
• Our implementation of deletion also depends on how we are representing lists (with a wrapper class, or just with the `Node` class).
• Using our standard the wrapper class, deletion at the head is relatively easy: we just update the front to the thing next to the front.
```/**
* Delete the first element in the list, returning that element.
* pre: the list is nonempty
* post: the list is one element smaller and no longer
*   contains the previous head element
public Object deleteHead() throws ListException {
// Special case: the list is empty
if (front == null) {
throw new ListException("Can't delete the head of an empty list");
}
// Standard case: the list is nonempty
else {
Node deleted = front;
// Update the front
front = front.nextNode();
// Update current, if necessary. == is the appropriate comparison
// operation since we want to make sure that we're referring to
// the same node, and not just two nodes with the same contents.
if (current == deleted) {
current = front;
}
// Return the front element
return deleted.contents();
} // else
```
• Note that while I've used methods of my `Node` class, it would also be reasonable to directly access `Node`'s fields, since these are related classes, and would be in the same package.
• Head-deletion in the `Node`-based version is really just a call to `tail`. To delete the head of a list `l`, you would just use
`l = l.tail()`
• Deletion from the end of a list is more difficult, as you need to step through until are two back from the end of the list.
```/**
* Delete the last element in the list, returning that element.
* pre: the list is nonempty
* post: the list is one element smaller and no longer
*   contains the previous final element
* post: if the current element was the final element, the
*   current element is now the first element.
*/
public Object deleteLast() throws ListException {
// The element we're about to delete
Node deleted;
// Special case: the list is empty
if (front == null) {
throw new ListException("Can't delete the last element in an empty list");
}
// Special case: the list has one element
else if (front.next == null) {
deleted = front;
front = null;
current = null;
back = null;
return deleted.contents();
} // only one element
// Standard case: the list has at least two elements
else {
// The element we're currently looking at
Object pointer = front;
// The element we're about to delete
Object
// Keep going until we reach the last node
while (pointer.nextNode().nextNode() != null) {
pointer = pointer.nextNode();
} // while
// We're now one step back from the end, so delete
// the next element.
deleted = pointer.next;
pointer.next = null;
// Update the current element if necessary.
if (current == deleted) {
current = front;
}
// Return the element
return deleted.contents();
} // List of length two or more
} // deleteLast
```
• Deleting all copies of a particular element can be somewhat more difficult, since it may require the "double next" pointer strategy used above.
• However, we can simplify our implementation by using a recursive `Node`-based deletion.
• Here are the two related functions
```/**
* Delete all copies of an element from a sequence of `Nodes`.
* pre: (none)
* post: The returned list no longer contains any copies of the element.
* post: Any lists that contain sublists may also be modified.
*/
public Node deleteCopies(Node list, Object element) {
// Base case: empty list
if (list == null) {
return null;
}
// The head of the list contains the element.  Delete and
// recurse
else if (list.contents().equals(element)) {
return deleteCopies(list.tail(), element);
}
// The head of the list doesn't contain the element.  Work on
// the tail.
else {
list.setNext(deleteCopies(list.tail(), element));
return list;
}
} // deleteCopies

/**
* Delete all copies of an element from a list.
* pre: (none)
* post: The returned list no longer contains any copies of the element.
* post: Any lists that contain sublists may also be modified.
* post: If the current element was deleted, the current element
*   now refers to the front of the list.
*/
public void deleteCopies(element) {
front = deleteCopies(front,element);
// Fix current element if necessary
if (current.contents().equals(element)) current = front;
// Recompute the last element if necessary
if (back.contents().equals(element)) recomputeBack();
} // deleteCopies
```

Outlines: prev next

[News] [Basics] [Syllabus] [Outlines] [Assignments] [Examples] [Readings] [Bailey Docs]

Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.