The list is a generalization of both stacks and queues. Stacks and queues are sequences of items from which we can remove items and into which we can insert items. But we are very restricted as to where these insertions and deletions may occur. In a stack, they occur at one end, called the top. In queues, insertions occur at one end, the rear, and deletions at the other, the front. We are also restricted as to which items are visible: in a stack, only the item at the top is visible; in a queue, only the item at the front (and possibly the one at the rear, depending upon who you ask) is visible.
If we lift these restrictions so as to allow insertion and deletion anywhere, plus the capability of navigation (i.e., "moving around" among the items), we get the concept of a list. Different varieties of lists are obtained by making different choices as to exactly which operations are included to provide these capabilities. The kind of list we present here represents only one such choice, which we refer to as the Positional List.
We can view a list as a sequence of "nodes", each of which contains a data item. Each node has a predecessor and a successor, with the exception of the first and last nodes. If we consider the first node to be the successor of the last, we arrive at the concept of a circular list or ring, as opposed to a grounded list.
Here is one possible depiction of a list of animals:
+---+ +---+ +---+ +---+ +---+ +---+ +---+
| C | | D | | B | | A | | O | | Y | | C |
| A |----| O |----| U |----| N |----| W |----| A |----| O |----x
| T | | G | | G | | T | | L | | K | | W | ^
+---+ +---+ +---+ +---+ +---+ +---+ +---+ |
^ ^ |
| | |
| | |
front current rear
position
In lists, unlike stacks and queues, observation and mutation may occur not only at the two ends but also in the middle. Thus, we need some means of maneuvering or navigating through the structure. To achieve this, we have the concept of a current position. Several of the operations that we will define on lists have an effect that is, in some sense, local to the current position. In our abstraction, a position corresponds either to a node or to a special position, called the rear, which comes "after" the last node. The unique position that does not follow any node is called the front. That is, if a list has at least one node, the front is the position occupied by the first such node; if a list is empty, the front coincides with the rear. In our example, the current position is at the node containing BUG.
Among the operations on lists are ones that cause the current position to change. This is how navigation takes place. Also, the current position acts as our "viewing window", in the sense that the only node whose contents we can observe is the one at the current position. (If we want to obtain the contents of some other node, we must first move there.)
One might question why we should have a single current position. Why not generalize the idea and make use of the concept of a tab or cursor such that we can define and manipulate as many of them as wanted for any particular list? For some applications, it would be natural to use at least two cursors. (E.g., to determine whether a list is the same as its own reversal, we could have two cursors start at opposite ends of the list and work towards each other, verifying at each step that the two nodes contain equal items.)
The answer to this question is that allowing the use of multiple tabs/cursors
Specification: Here's one possible informal specification for a list class consistent with the discussion above:
Observers:
boolean isEmpty() : answers the question "Is list empty?" int lengthOf() : yields # of nodes in list Object getObj() : yields contents of node at current position boolean atFront() : answers "Is current position the front of list?" boolean atRear() : answers "Is current position the rear of list?"Remarks: As noted before, in an empty list (i.e., one having length zero), the front and rear positions coincide. The only pre-condition for any of these operations is that atRear()) must not hold when getObj() is invoked.
Navigators (Positional Mutators):
void toFront() : sets current position to front of list void toRear() : sets current position to rear of list void toNext() : moves current position one place closer to rear void toPrev() : moves current position one place closer to frontRemarks: The pre-condition of toNext() (respectively, toPrev()) is !atRear() (respectively, !atFront()).
Node Mutators:
void replace(x) : replaces current node's contents with x
void insert(x) : inserts new node containing x; it becomes the
predecessor of the current position
void insertFront(x) : inserts new node containing x at front of list
void insertRear(X) : inserts new node containing x at rear of list
void remove() : removes the node at the current position; the
successor becomes the current position
Remarks: The precondition of both replace() and remove() is !atRear().
Arrays vs. Lists:
Among the distinctions between lists and arrays is that,
in an array, there is no notion of "deleting" or "inserting" an element.
You can place an item into an array element (which overwrites what is
already there), but you cannot "insert" a new element or delete an
existing element.
Having sketched a (rather large, compared to the ADT's we've already seen)
collection of operations, let's do a few list "applications".
Problem #0: Develop a method that finds the length of (i.e., the
number of nodes in) a list. (Ignore the fact that we stipulated the
existence of a lengthOf() operation!)
Problem #1: Develop a method that finds the sum of the values
in a list of items from the wrapper class Integer.
Here we see a design pattern that is quite common: traversing the list,
examining each node, and, in doing so, performing some operation that
helps in the accumulation of some result.
Note that, using the lengthOf() method, we could have written
the loop as a for loop, with the loop control variable varying
from, say, 0 to list.lengthOf()-1.
Trivial modifications to the program yield solutions to similar problems
such as, say, counting the number of nodes containing positive values.
Here, we would replace the assignment inside the loop with an if
statement
Problem #2: For each node in a given list of items from the
(wrapper) class Integer, add one to its contents
if it contains a positive value, subtract one if it contains a
negative value, and don't change it if it contains zero.
Problem #3:
Remove from a list any node containing the same value (as determined by
the equals() predicate) as its predecessor.
That is, the final state of the list should be such that it contains the
same sequence of values as originally, but with no consecutive duplicates.
Problem #4:
Assuming that the items in list are in ascending order with
respect to the Comparator c, insert a new item into its rightful place.
Problem #5: Sorting.
Given an array of Objects and a comparator c that defines a
total ordering upon them, construct an ascending-ordered list containing
the objects in the array.
Note that if the objects in the array are in "random" order,
the running time of the method above would be expected to be
proportional to the square of the length of the resulting list.
Letting n be the number of items ending up
in the list, we use the notation O(n2) to describe
this running time.
Problem #6: Partitioning.
Given a list of Objects, a Comparator, and a
"pivot" object, rearrange the items in the list so that, upon completion,
all those less than the pivot precede the current position and all those
not less than the pivot follow it.
Applications of Lists
Solution:
public static int length( PositionalList list ) {
int cntr = 0;
list.toFront();
// loop invariant: cntr = # of nodes preceding current position
while ( !list.atRear() ) {
cntr = cntr + 1;
list.toNext();
}
return cntr;
}
Solution:
public static int sumOf( PositionalList<Integer> list ) {
int sum = 0;
list.toFront();
while ( !list.atRear() ) {
sum = sum + list.getObj().intValue();
list.toNext();
}
return sum;
}
if (list.getObj().intValue() > 0)
{ cntr = cntr + 1; }
where cntr is a local variable whose value would, after
termination of the loop, be returned as the method's result.
Solution:
public static void change( PositionalList<Integer> list ) {
list.toFront();
while ( !list.atRear() )
int y = list.getObj().intValue();
if (y < 0)
{ list.replace(new Integer(y-1)); }
else if (y > 0)
{ list.replace(new Integer(y+1)); }
else // y = 0
{ }
list.toNext();
}
}
Solution:
public static void removeAdjacentDuplicates(PositionalList list) {
if (list.isEmpty())
{ }
else {
int prevItem, crrntItem;
list.toFront();
prevItem = list.getObj();
list.toNext();
/* loop invariant: there are no adjacent duplicates in that
* portion of list preceding the current position &&
* prevItem equals the value in the predecessor node
*/
while ( !list.atRear() ) {
crrntItem = list.getObj();
if ( prevItem.equals(crrntItem) )
{ list.remove(); }
else {
prevItem = crrntItem;
list.toNext();
}
}
}
}
Solution:
/* pre: list is in ascending order (with respect to c)
** post: list is still in ascending order, and is the same as before,
** except that a new node containing k appears in it
*/
public static void orderedInsert(PositionalList list,
Comparator c,
Object obj) {
list.toFront();
/* loop invariant:
All nodes preceding current position contain values less than obj
*/
while (!list.atRear() && c.compare(obj, list.getObj()) > 0)
{ list.toNext(); }
// assertion: either
// (1) list.atRear() and obj is larger than every item in list, or
// (2) !list.atRear() and the current node is first one containing
// a value greater than or equal to obj
list.insert( obj );
}
Solution:
public static PositionalList sort(Object[] a, Comparator c) {
PositionalList list = new PositionalListViaX();
for (int i=0; i != a.length; i = i+1) {
orderedInsert(list, a[i]); // from previous problem
}
return list;
}
Solution:
public static void partition(PositionalList list,
Comparator c,
Object pivot) {
list.toFront();
// loop invariant:
// list.length() is unchanging and
// all items preceding current position are less than pivot and
// # items less than pivot following current position is at most i
int len = list.length;
for (int i = len; i > 0; i = i-1) {
Object item = list.getObj();
if (c.compare(item, pivot) < 0)
{ list.toNext(); }
else {
list.remove();
list.insertAtRear(item);
}
}
Implementation/Data Representation
Click here for an array-based
representation.