CS 261 University of Puget Sound Spring, 2020
 
Computer Science II
Abstract Data Types and their Implementations, Some Basic Algorithms,
Object-oriented Problem Solving, and Efficiency
 

Warning: This course is under development. Although the basic structure of this course is largely established, nothing on this Web site should be considered official or even possibly correct.
DO NOT MAKE PLANS BASED ON THE CONTENTS OF THIS SITE UNTIL JANUARY, 2020.

Linked Lists in Java using Iteration

Introduction

The previous reading on Simple lists introduced the concept of a linked list and considered several basic operations:

This reading extends the range of list operations and includes additional examples.

In each case, iterative algorithms are considered. (We'll examine another approach, called recursion, later in the course.)

All methods in this reading are gathered together in the complete class NameList.java, based upon the node class ListStringNode.java.

Reading Overview


List Traversals

A list traversal is the process of moving from node-to-node in a linked list to perform some type of processing. For example, the reading on simple lists discussed printing the data on a list using a loop that moved a list pointer nodeRef systematically from one node to the next, printing a data field at each node.

The printing example used a while loop to examine when a list pointer was pointing to a node. Another example of this approach, called an iterative algorithm involves examining the strings stored in list nodes to determine which comes first in alphabetical order.

Jargon

An algorithm involving a loop (e.g., for or while) to move from one case to the next is said to be iterative.

An algorithm that proceeds by identifying one or more simple cases and reducing complex cases to simpler ones — using the function to call itself — is said to be recursive. The simple case or cases are said to be base case(s), and the reduction from complex situations to simpler ones is said to be the general or recursive case.

This lab focuses upon iterative algorithms for list processing. Work later in the semester will utilize recursion for some list processing.


Iterative Traversals

Consider the following problem:

Examining a String to Find the First String in Dictionary Order

When searching a 1-dimensional array for a minimm, a for or while loop typically allows a program to examine each of the n values in an array. For linked lists, we may not know directly the number n of nodes on a list. Instead, we use a pointer variable nodeRef to start at the head of the list, and processing can proceed node-by-node until nodeRef becomes null. Except for the mechanism to move from one data value to the next, the resulting code parallels the earlier code for an array of int values.

In what follows, we assume a ListNode has been created with getData and getNext methods.

Dictionary Order

In ordering string data, a common approach compares two strings character by character.

This approach to ordering strings is commonly used in dictionaries, so this sequencing of strings is often called dictionary order.


   /* post-condition:  The string coming first in dictionary order is returned
      note:  processing proceeds iteratively
   */
   public void findMinimum( ) {
     if (first == null) {
      System.out.println ("list is empty:  no string comes first in dictionary order");
     }

Program Notes


     else {
        ListStringNode nodeRef = this.first;
        ListStringNode minRef = this.first;
        while (nodeRef != null) {
 

            if (minRef.compareTo (nodeRef) > 0) {
              minRef = nodeRef;
           }
            nodeRef = nodeRef.getNext ( );
        }


        System.out.println ("The minimum string on the list (dictionary order) is: "
                          + minRef.getData ( ));
    }
}

Insertion into a List

The insertion of an item into a list may be done either iteratively or recursively; the overall result is illustrated in the figure below, where "Visit Library" is inserted into a new third box on the list.

Acknowledgment

Much of the discussion of insertion into a list and deletion from a list within this reading is edited from "Computer Science 2: Principles of Software Engineering, Data Types, and Algorithms" by Henry M. Walker, Scott, Foresman and Company, 1989, and is used with permission of the copyright holder.


inserting an item into a linked list

In the new list, we created a new box, placed the "Visit Library" in the data field, made the pointer of the new box indicate that the "Read email" box comes next, and changed the pointer of the "Attend class" box to this new list item. In this insertion process, we can build the "Visit Library" box at any convenient place; then we add this box to the list by changing pointers appropriately.

This example illustrates the main steps involved in the iterative algorithm for inserting an item into a list. The following figure shows these steps in some detail.

inserting an item into a linked list

Within this outline of steps for iterative insertion, two complications arise:

Often, in inserting a new item into a list, it is convenient to identify the node that should come after the new one. In that circumstance, when searching for the location to insert the new list, we will need to keep track of the previous node as well — Step 5 in the insertion process requires that we update the next field of the previous node, so we need to know where that node is located. (If we find the node after the new node, we can follow next fields to go to toward the end of the list, but we have no mechanism to back up.)


Iterative Insertion

The following iterative procedure addName inserts an item before a designed node.


 /* pre-condition: none
   post-condition:  a designated newName is inserted into the list 
        at a designated place */
   void addName(String newName) {
          
      ListStringNode newNode = new ListStringNode (newName, null);
      ListStringNode nodeRef;

      if (this.first == null) {
         /* insert name's node at start of list */
         this.first = newNode;
      } 

  
    else {
         String oldName ;
         System.out.println ("Enter old name which new name should preceed, ");
         System.out.println ("or enter ? if new name should be placed last");
         Scanner fileIn = new Scanner (System.in);
         oldName = fileIn.next ( );

         if (oldName.compareTo (first.getData ( )) == 0) {
            /* insert name's node at start of list */
            newNode.setNext (this.first);
            this.first = newNode;
         }

  else {
            /* insert name's node after start of the list */
            ListStringNode prevRef;
           
            /* start at beginning of list */
            nodeRef = first.getNext ( );  /* the current node to search */
            prevRef = this.first;          /* the node behind nodeRef */

            while ((nodeRef != null)
                   && (oldName.compareTo (nodeRef.getData ( )) != 0)) {
               prevRef = nodeRef;
               nodeRef = prevRef.getNext ( );
            }

            newNode.setNext (prevRef.getNext ( ));
            prevRef.setNext (newNode);
            }
       }
       System.out.println (newNode + " inserted into the list");
   }

Deletion from a List

The overall strategy for deletion is illustrated in the following figure, in which the third item is deleted from the list.

an overview of deletion from a list

In the new list, the box, "Read email", is deleted from the list. In the second list, the "Attend class" box no longer specifies that the "Read email" box comes next; rather the pointer for the "Attend class" box indicates that the "Eat lunch" box comes next. With a small change in a pointer, the "Read email" box is no longer on the list, since we cannot reach that box by starting at the beginning of the list and moving from one item to the next. Even if the box is physically present somewhere, it is lost for all future work because there is no way to locate it. Beyond changing pointers, we also may decide to throw away the old item that we deleted so that we can use that space again.

This example illustrates the main steps involved in the iterative algorithm for deleting an item from a linked list. The following figure shows these steps more carefully.

an overview of deletion from a list

As the figure suggests, the ideas behind this deletion are fairly simple. However, the conceptual approach is complicated by two details:

This second point emphasizes that in moving through linked lists, it is easy to go from any particular list item to later ones by following the arrows. We cannot follow the arrays backward, however, so it is difficult to back up toward the front. Each list item contains information about the next item, but there is no information about previous ones. Thus, to delete a list item, we must explicitly keep track of previous items as we search. With the preceding item, we can move ahead easily to the item we will actually delete.

Iterative Deletion

These comments motivate the following deleteName function.


  /* post-condition:  a node with the given deletionString is deleted 
         from the list */
   void deleteName(String deletionString) {
      if (this.first == null) {
         System.out.println("List is empty - no deletions are possible");
       } 

    else {
          if (deletionString.compareTo (first.getData ( )) == 0) {

             /* remove first item on list */
             this.first = this.first.getNext ( );
             System.out.println (deletionString + " removed as first item on list");
          }

            /* item to remove is not at beginning of list */
            /* start at beginning of list */
            ListStringNode nodeRef = this.first.getNext ( );  /* the current node to search */
            ListStringNode prevRef = this.first;   /* the node behind nodeRef */

            while ((nodeRef!=null)
                   && (deletionString.compareTo(nodeRef.getData ( )) != 0)) {
               prevRef = nodeRef;
               nodeRef = prevRef.getNext ( );
            }

           if (nodeRef != null) {
              /* remove item from list */
              prevRef.setNext (nodeRef.getNext ( ));
              System.out.println (deletionString + " deleted from list");
           } else {
              System.out.println (deletionString +"not found on list");
           }
       }
    } /* end processing of deletionString */   

      else {
        printf ("%s not found on list\n\n", dataString);
      }
    } /* end processing of dataString */
  }
}

Full Program

Reminder: As noted several times in the above discussion, program NameList.java compiles all functions in this reading into a full program.



created 26 November 2016 by Henry Walker
revised to move recursive discussion to separate document 20-27 January 2020 by Henry Walker
Valid HTML 4.01! Valid CSS!
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu.