In this module we present one of the two traditional implementations
for lists, usually called a linked list.
The linked list uses dynamic memory allocation,
that is, it allocates memory for new list elements as needed.
The following diagram illustrates the linked list concept.
Here there are three nodes that
are “linked” together.
Each node has two boxes.
The box on the right holds a link to the next node in the list.
Notice that the rightmost node has a diagonal slash through its link
box, signifying that there is no link coming out of this box.
Because a list node is a distinct object (as opposed to simply a cell
in an array), it is good practice to make a separate list node class.
(We can also re-use the list node class to implement linked
implementations for the stack and
queue data structures.
Here is an implementation for list nodes, called the Link class.
Objects in the Link class contain an element field to
store the element value, and a next field to store a pointer to
the next node on the list.
The list built from such nodes is called a singly linked list,
or a one-way list, because each list node
has a single pointer to the next node on the list.
classLink{// Singly linked list node classprivateObjecte;// Value for this nodeprivateLinkn;// Point to next node in list// ConstructorsLink(Objectit,Linkinn){e=it;n=inn;}Link(Linkinn){e=null;n=inn;}Objectelement(){returne;}// Return the valueObjectsetElement(Objectit){returne=it;}// Set element valueLinknext(){returnn;}// Return next linkLinksetNext(Linkinn){returnn=inn;}// Set next link}
classLink<E>{// Singly linked list node classprivateEe;// Value for this nodeprivateLink<E>n;// Point to next node in list// ConstructorsLink(Eit,Link<E>inn){e=it;n=inn;}Link(Link<E>inn){e=null;n=inn;}Eelement(){returne;}// Return the valueEsetElement(Eit){returne=it;}// Set element valueLink<E>next(){returnn;}// Return next linkLink<E>setNext(Link<E>inn){returnn=inn;}// Set next link}
The Link class is quite simple.
There are two forms for its constructor, one with
an initial element value and one without.
Member functions allow the link user to get or set the element
and link fields.
Here is a graphical depiction for a linked list storing five integers. The value stored in a pointer variable is indicated by an arrow "pointing" to something. A NULL pointer is indicated graphically by a diagonal slash through a pointer variable's box. The vertical line between the nodes labeled 23 and 10 indicates the current position (immediately to the right of this line).
There are a number of problems with the representation just
described.
First, there are lots of special cases to code for.
For example, when the list is empty we have
no element for head, tail, and curr to point to.
Implementing special cases for insert and remove
increases code complexity, making it harder to understand,
and thus increases the chance of introducing bugs.
Another problem is that we have no link to get us to the preceding node (shown in yellow). So we have no easy way to update the yellow node's next pointer.
Fortunately, there is a fairly easy way to deal with all of the
special cases, as well as the problem with deleting the last node.
Many special cases can be eliminated by implementing
linked lists with an additional header node
as the first node of the list.
This header node is a link node like any other, but its value is
ignored and it is not considered to be an actual element of the list.
The header node saves coding effort because we no longer need to
consider special cases for empty lists or when the current position is
at one end of the list.
The cost of this simplification is the space for the header node.
However, there are space savings due to smaller code size,
because statements to handle the special cases are omitted.
We get rid of the remaining special cases related to being at the end
of the list by adding a “trailer” node that also never stores a
value.
The following diagram shows initial conditions for a linked list
with header and trailer nodes.
null
null
head
curr
tail
Here is what a list with some elements looks like with the header and
trailer nodes added.
null
20
23
10
12
15
null
head
curr
tail
Adding the trailer node also solves our problem with deleting the last
node on the list, as we will see when we take a closer look at the
remove method’s implementation.
importjava.util.NoSuchElementException;// Linked list implementationclassLListimplementsList{privateLinkhead;// Pointer to list headerprivateLinktail;// Pointer to last elementprivateLinkcurr;// Access to current elementprivateintlistSize;// Size of list// ConstructorsLList(intsize){this();}// Constructor -- Ignore sizeLList(){clear();}// Remove all elementspublicvoidclear(){curr=tail=newLink(null);// Create trailerhead=newLink(tail);// Create headerlistSize=0;}// Insert "it" at current positionpublicbooleaninsert(Objectit){curr.setNext(newLink(curr.element(),curr.next()));curr.setElement(it);if(tail==curr)tail=curr.next();// New taillistSize++;returntrue;}// Append "it" to listpublicbooleanappend(Objectit){tail.setNext(newLink(null));tail.setElement(it);tail=tail.next();listSize++;returntrue;}// Remove and return current elementpublicObjectremove()throwsNoSuchElementException{if(curr==tail)// Nothing to removethrownewNoSuchElementException("remove() in LList has current of "+curr+" and size of "+listSize+" that is not a a valid element");Objectit=curr.element();// Remember valuecurr.setElement(curr.next().element());// Pull forward the next elementif(curr.next()==tail)tail=curr;// Removed last, move tailcurr.setNext(curr.next().next());// Point around unneeded linklistSize--;// Decrement element countreturnit;// Return value}publicvoidmoveToStart(){curr=head.next();}// Set curr at list startpublicvoidmoveToEnd(){curr=tail;}// Set curr at list end// Move curr one step left; no change if now at frontpublicvoidprev(){if(head.next()==curr)return;// No previous elementLinktemp=head;// March down list until we find the previous elementwhile(temp.next()!=curr)temp=temp.next();curr=temp;}// Move curr one step right; no change if now at endpublicvoidnext(){if(curr!=tail)curr=curr.next();}publicintlength(){returnlistSize;}// Return list length// Return the position of the current elementpublicintcurrPos(){Linktemp=head.next();inti;for(i=0;curr!=temp;i++)temp=temp.next();returni;}// Move down list to "pos" positionpublicbooleanmoveToPos(intpos){if((pos<0)||(pos>listSize))returnfalse;curr=head.next();for(inti=0;i<pos;i++)curr=curr.next();returntrue;}// Return true if current position is at end of the listpublicbooleanisAtEnd(){returncurr==tail;}// Return current element value.publicObjectgetValue()throwsNoSuchElementException{if(curr==tail)// No current elementthrownewNoSuchElementException("getvalue() in LList has current of "+curr+" and size of "+listSize+" that is not a a valid element");returncurr.element();}// Check if the list is emptypublicbooleanisEmpty(){returnlistSize==0;}}
importjava.util.NoSuchElementException;// Linked list implementationclassLList<E>implementsList<E>{privateLink<E>head;// Pointer to list headerprivateLink<E>tail;// Pointer to last elementprivateLink<E>curr;// Access to current elementprivateintlistSize;// Size of list// ConstructorsLList(intsize){// Constructor -- Ignore sizethis();}LList(){clear();}// Remove all elementspublicvoidclear(){curr=tail=newLink<E>(null);// Create trailerhead=newLink<E>(tail);// Create headerlistSize=0;}// Insert "it" at current positionpublicbooleaninsert(Eit){curr.setNext(newLink<E>(curr.element(),curr.next()));curr.setElement(it);if(tail==curr){tail=curr.next();// New tail}listSize++;returntrue;}// Append "it" to listpublicbooleanappend(Eit){tail.setNext(newLink<E>(null));tail.setElement(it);tail=tail.next();listSize++;returntrue;}// Remove and return current elementpublicEremove()throwsNoSuchElementException{if(curr==tail){// Nothing to removethrownewNoSuchElementException("remove() in LList has current of "+curr+" and size of "+listSize+" that is not a a valid element");}Eit=curr.element();// Remember valuecurr.setElement(curr.next().element());// Pull forward the next elementif(curr.next()==tail){tail=curr;// Removed last, move tail}curr.setNext(curr.next().next());// Point around unneeded linklistSize--;// Decrement element countreturnit;// Return value}publicvoidmoveToStart(){curr=head.next();// Set curr at list start}publicvoidmoveToEnd(){curr=tail;// Set curr at list end}// Move curr one step left; no change if now at frontpublicvoidprev(){if(head.next()==curr){return;// No previous element}Link<E>temp=head;// March down list until we find the previous elementwhile(temp.next()!=curr){temp=temp.next();}curr=temp;}// Move curr one step right; no change if now at endpublicvoidnext(){if(curr!=tail){curr=curr.next();}}publicintlength(){returnlistSize;}// Return list length// Return the position of the current elementpublicintcurrPos(){Link<E>temp=head.next();inti;for(i=0;curr!=temp;i++){temp=temp.next();}returni;}// Move down list to "pos" positionpublicbooleanmoveToPos(intpos){if((pos<0)||(pos>listSize)){returnfalse;}curr=head.next();for(inti=0;i<pos;i++){curr=curr.next();}returntrue;}// Return true if current position is at end of the listpublicbooleanisAtEnd(){returncurr==tail;}// Return current element value. Note that null gets returned if curr is at the tailpublicEgetValue()throwsNoSuchElementException{if(curr==tail)// No current element{thrownewNoSuchElementException("getvalue() in LList has current of "+curr+" and size of "+listSize+" that is not a a valid element");}returncurr.element();}//Tell if the list is empty or notpublicbooleanisEmpty(){returnlistSize==0;}}