Linked Lists

The ListDeque Application

The examples in this, the previous, and the next document
all part of the Java Application
ListDeque.
Download the source archive
ListDeque.zip
and install it as a Java Application with Existing Sources. See the
Using NetBeans
document for details.

Like others, the ListDeque project has multiple Main Classes
intended to illustrate various independent features. The simplest way to
run the various classes
is to right-click the file and select Run File either in the
file itself or from the listing in the Projects window.

Description

The goals and methods for creating user-defined Linked Lists
are similar to those described in
the Array Lists document.
We will create a LListAdapter class from which we
can derive our implementation classes.
We are interested in linked lists created both from singly-linked
(forward-only) node structures as well as doubly-linked
node structures.

Comparison of LinkedList/ArrayList

The linking structure of linked lists creates different
behavior from array lists, here are the key difference:

ArrayList

LinkedList

get/set access

accessing an element (get/set) at an index is O(1);
for this reason the ArrayList implements the
RandomAccess interface

accessing an element (get/set) at an index is O(n) because we must proceed through
a sequence of node pointers to get to the correct position.

add/remove at first and last positions

add at the last position is
O(1) in general; an additional
O(n) cost is incurred
when the array's capacity is increase;
if the capacity is maintained efficiently, the overall average cost is
still O(1)

remove at the last position is always
O(1)

add/remove at the first position is always
O(n)
because the entire array is shifted

add/remove at the first or last positions is
O(1)
(with doubly-linked lists)

add/remove at arbitrary position

add/remove at arbitrary position is
O(n) due to shifting

add/remove at arbitrary position is O(n)
due to sequencing through pointers

space usage

wasted space varies according to the number of
unused array positions; in order to keep
average add time efficient,
O(n) wasted space
must be created at capacity increases

the links are "wasted" by virtue of not holding data; thus
there is always
O(n)
wasted space

In general terms, an ArrayList
is a better choice when we're interested in
"random positional" access of elements whereas a
LinkedList is better suited for end-based access.

which would give us access to all the relevant operations, but in
practice one tends to focus on one the Queue, Deque,
or List interface operations exclusively.

The Queue and Deque interfaces offer a bewildering
array of choices of member functions, as described in the
Java Data Structures (java.util)
document.
Here is a table of operation descriptions focusing on the non-index
based operations. Keep in mind that first and last
signify the leftmost and rightmost elements, respectively.

The main difference between the "remove_" and "get_"
methods versus the "poll_" and "peek_" operations is
that for the former ones, an exception is thrown if the list is empty,
whereas for the latter, a null is returned.

Demo Programs

Start by observing and running the main classes demo.LListDemo1.

demo.LListDemo1

and

demo.LListDemo2

Linked List Types

Singly Linked

A singly-linked list represents a minimalistic linked representation.
The entire list is accessed by the first pointer.
A single next element within each node
moves forward in the list and all nodes contain
list element data. The list is null-terminated.
The empty list is simply the null pointer.
A list created in this way is recursive, and functions which act
on it are well suited to using recursion.

singly-linked, without header

(8,4,9):

Empty List:

An alternative structure uses an always-present "dummy" node which first
points to. The data in the dummy node is null and is ignored.
The list is null-terminated.
The empty list points to the dummy node which has next == null.

singly-linked, with header

(8,4,9):

Empty List:

Doubly Linked

A doubly linked list is accessed by first and last pointers. Going forward and
backwards are treated symmetrically via node pointers
next (forward) and prev (backward).

The first and last pointer always point to the
respective dummy nodes at the beginning and end, respectively.
Using this structure, insertions are treated equally,
regardless of the position; the same is true of deletions.

doubly-linked, with header/footer

(8,4,9):

Empty List:

Singly-linked List Implementation

Our LListAdapter provides vacuous definitions for all the functions
satisfied by LinkedList. The functions are all "unimplemented"
in the sense that their only code is to throw an UnsupportedOperationException.
Both of our user-defined versions will be extensions of LListAdapter.
The Serializable implementation is listed, but
the implementations we give are, in reality, not
serializable, since they are based on Java memory references.

adapter.LListAdapter

Our first stab at an implementation uses a singly-linked list. Both
the with-header and without-header have advantages and disadvantages.

If we want to make the size() member function more efficient,
an int size data member should also be maintained.
A Node last data member could be added to make
the addLast operation more efficient, but it would offer
no support for the removeLast operation, since we cannot
easily "back up" from the end. Furthermore, a last data member
actually detracts from the simplicity of the class member functions.

Testing

Replace LinkedList by SingleLList in
LListDemo1, changing the definition of L:

From this point, write additional methods in the SingleLList
class (whether they are List interface methods or not), and
test them by writing method calls in the SingleLListTesting main class.
For example, if you write the print method, then add the
following line to the main method:

demo.SingleLListTesting

ints.print();

The Recursive Version

The iterative implementations we write iterate through some portion of
the node chain. When you write a recursive version of a method, the
node iteration is replaced by function calls to a helper function
which has an additional Node parameter like this: