Lecture 9 -- Linked Lists
Linked Lists
Consider the array implementation of List. It provides efficient
access to individual data items by index; in particular, the get and set
methods are fast. However, it has some shortcomings:- The resize operation is expensive (in execution time).
- Adding at the beginning or middle of the list is expensive.
- Removing from the beginning or middle of the list is expensive.
On the other hand, it is the contiguous nature of the array that is responsible for its drawbacks. An alternative is the linked list. A linked list is comprised of a series of memory blocks called nodes. Each node contains one data item along with a reference ("link") to the next node in the list, as shown below:
Some properties of linked lists:
- Items need not be stored contiguously. The logical order of the list may not be the same as its physical order.
- To maintain a logical ordering, each item has a reference to the next item.
- Items may be removed from the list by skipping over them in the link path.
- Items may be added to the list by linking them in; that is, modifying a few of the links.
- Sequential traversal (in logical order) can be performed by following the link path.
Several variations on the basic linked list are possible, such as
- Maintaining both a head and tail pointer.
- Using a sentinel node (dummy node) at the head or tail of the list.
- Circular linked list.
- Doubly-linked list.
/*
Linked List implementation. This implementation uses a local ListNode class.
Each ListNode contains one data item and a link to the next node in the
list. The LinkedList class itself contains references to the head node and
the tail node in the list. It also keeps a count of the number of items in
the list.
*/
public class LinkedList implements SimpleList
{
int nitems;
ListNode head,tail;
public class ListNode {
Object datum;
ListNode next;
ListNode(Object datum){
this.datum = datum;
next = null;
}
ListNode(Object datum, ListNode next){
this.datum = datum;
this.next = next;
}
public Object getDatum(){
return datum;
}
public ListNode getNext(){
return next;
}
}
LinkedList()
{
nitems = 0;
head = null;
tail = null;
}
private ListNode getnth(int index)
{
ListNode target = head;
if(index<0 || index>=nitems)
throw new IndexOutOfBoundsException();
for(int k=0; k<index; k++)
target = target.next;
return target;
}
public void clear()
{
nitems=0;
head=tail=null;
}
public int size()
{
return nitems;
}
public boolean isEmpty()
{
return head==null;
}
public void prepend(Object item)
{
if(isEmpty())
head = tail = new ListNode(item);
else
head = new ListNode(item,head);
++nitems;
}
public boolean add(Object item)
{
if(isEmpty())
head = tail = new ListNode(item);
else {
tail = tail.next = new ListNode(item);
}
nitems++;
return true;
}
public Object get(int index)
{
return getnth(index).datum;
}
public Object set(int index, Object obj)
{
ListNode node = getnth(index);
Object temp = node.datum;
node.datum = obj;
return temp;
}
public Object remove(int index)
{
Object target;
if(index<0 || index>=nitems)
throw new IndexOutOfBoundsException();
else if(index==0){
target = head.datum;
head = head.next;
if(nitems==1)
tail = null;
}
else {
ListNode prior = getnth(index-1);
target = prior.next.datum;
prior.next = prior.next.next;
if(nitems==index+1)
tail = prior;
}
--nitems;
return target;
}
public void add(int index, Object item)
{
if(index<0 || index>nitems)
throw new IndexOutOfBoundsException();
else if(index==nitems)
add(item);
else if(index==0)
prepend(item);
else {
ListNode prior = getnth(index-1);
prior.next = new ListNode(item,prior.next);
++nitems;
}
}
}
Some advantages of linked lists:
- Access by pointer is faster than array indexing.
- Add and remove don't require shuffling data.
- They use only needed storage.
- Efficient operations: adding or removing the first element; appending one list to another.
- Process in forward order only (unless doubly-linked).
- Extra storage required for links.
- Inefficient operations: access to the nth element.