4.1 Introduction
In this chapter we will start to look at some of the standard data types of computer science.
These types are important in almost all computer languages. We will look at how these
types are provided by the Java library and at how we can implement them ourselves.
The first data types that we look at will all be used to hold collections of data. For
example we may want to use a list of names or a set of numbers.
The Java library provides data types for collections as a number of classes and interfaces
in the java.util package. This is called the collections framework.
The term abstract data type means that we will separate the specification of the data
type from its implementation. The specification includes a description of what the type
does and the methods it provides to do it. The implementation provides the code that
actually does the work.
For most ADTs there are more than one implementation provided. Different implemen-
tations may be more efficient in different circumstances, for example some may use less
memory or be faster at certain operations. Because the specification and implementation
are separate it should be possible for a user of an ADT to change implementation with
very little change to their code.
Java provides two ways of specifying a method independently from the implementation,
interfaces and abstract classes. The collections framework is based on interfaces, but a few
abstract classes are provided for convenience.
List A numbered collection of items. Items can be added or removed at any place in the
list.
1
CHAPTER 4. ABSTRACT DATA TYPES 2
Stack A last in first out list (LIFO). Items can only be added or removed from the top.
Queue A first in first out list (FIFO). Items can be added at one end and removed at the
other.
4.2 Lists
4.2.1 Introdution to Lists
We will first look at the ADT List in detail.
A list is used to hold a numbered collection of items. For example here is a list of four
Strings
0 first
1 second
2 third
3 last
The strings have a position or index. In Java we number from zero, so first has position
0 and third has position 2.
A typical list has methods to add or remove items from any position. When an item is
removed the items below must move up. When one is added they must move down. There
would also be methods to get an item from any position without removing it, and to set a
value at a position. There would be a method to get the size of the list.
Lists come in many versions. For an immutable list we would be able to get the value
from any position but not to change the list. Other lists may allow changing of the values
but not changing the size of the list.
E set(int index, E element) Sets the element at the given index, returns the old value.
void add(int index, E element) Adds a new element at the given index, moves the old
element and any later elements down one.
E remove(int index) Removes the element at the given index and moves any lower ele-
ments up one. Returns the element it removes.
CHAPTER 4. ABSTRACT DATA TYPES 4
All these methods should throw an exception if the index given is out of range. The
range is 0<=index<size() except for add where it is 0<=index<=size() as you are allowed
to add at the end.
The mutator methods set, add, remove would not be used for an immutable list. As
the Java library List interface requires them these methods need to be provided. They
should do nothing except throw a UnsupportedOperationException.
A list is allowed to reject add or set methods if the types of the elements are unsuitable.
If your List represents a list of Strings you can make it reject other types. It should throw
a ClassCastException or a NullPointerException.
Most of the other methods in the Java List iterface could be written using these ba-
sic methods, however it is often possible to write more efficent methods for a particular
implementation.
Most lists would also have:
boolean add(E o) Adds an E to the end of the list. A list version would normally be a
void method. The Java library version returns a boolean to indicate if anything is
added. This is always true for lists, it can be false for sets if the element is already
there.
boolean isEmpty() returns true if the list is empty.
void clear() Remove all the elements.
boolean contains(Object o) Returns true if the list contains this object. (That is if
there is an entry element in the list with element.equals(o) true.
4.2.3 Iterators
We often want to do an operation for every element of the list in order. We can do this
with a for loop, for example if list is a list of strings we can use:
You should not modify a list while using an iterator except by using its remove()
method.
Our previous example would become:
f o r ( Iterator < String > it = list . iterator (); it . hasNext ();)
System . out . println (" Next element is "+ it . next ());
In Java 1.5 the extended for loop has a special case to cover this common code.
f o r ( String s : list )
System . out . println (" Next element is "+ s );
There is an extended version of an iterator for lists called ListIterator<E> that can
go forwards and backwards and make more modifications to the list.
Constructors
A list class without constructors is not much use to us. Java interfaces cannot include
constructors, so they are not part of the interface. The documentation indicates that a list
class should have at least two constructors
The Java 1.5 autoboxing and unboxing feature makes it easy to use wrapper classes.
List < Integer > list =new ArrayList < Integer >();
list . add (new Integer (27));
list . add (27); // Same as above
i n t m = list . get (0). intValue ();
i n t n = list . get (0); // Same as above
CHAPTER 4. ABSTRACT DATA TYPES 6
E remove(int index)
We will also override a few more of the methods to show how they work.
package com328 . structure ;
import java . util . AbstractList ;
import java . util . Collection ;
import java . util . Iterator ;
/* * MyArrayList
* @author C.T. Stretch
*/
p ub li c c l a s s MyArrayList <E > extends AbstractList <E >
{
f i n a l s t a t i c i n t FIRST_SIZE = 16;
p r i v a t e E [] theArray ;
p r i v a t e i n t size ;
p ub li c E remove ( i n t index )
{
i f ( index < 0 || index >= size )
throw new IndexOutOfBoundsException ("" + index );
CHAPTER 4. ABSTRACT DATA TYPES 8
E r = theArray [ index ];
f o r ( i n t i = index + 1; i < size ; i ++)
theArray [ i - 1] = theArray [ i ];
size - -;
return r ;
}
p ub li c E get ( i n t index )
{
i f ( index < 0 || index >= size )
throw new IndexOutOfBoundsException ("" + index );
return theArray [ index ];
}
p ub li c i n t size ()
{
return size ;
}
p ub li c boolean isEmpty ()
{
return size ==0;
}
{
f o r ( i n t i =0; i < size ; i ++)
{ Object x = theArray [ i ];
i f ( x != n u l l && x . equals ( obj )) return true ;
}
return f a l s e ;
}
p ub l i c void remove ()
{
i f ( canRemove )
{
MyArrayList . t h i s . remove ( - - pos );
canRemove = f a l s e ;
}
else
throw new IllegalStateException ();
}
p ub l i c boolean hasNext ()
{
CHAPTER 4. ABSTRACT DATA TYPES 10
p ub l i c E next ()
{
canRemove = true ;
return theArray [ pos ++];
}
}
}
We can write a linked list using a class Node with two fields:
c l a s s Node <E >
{
Node <E > next ;
E data ;
}
The next field holds a reference to the next node in the chain, the data field holds the
data for that node. The next field of the last node is usually null.
A linked list implementation has advantages and disadvantages over an array imple-
mentation.
Advantages are that we never need to copy the data when we increase the size of the
list, we just add another node. Similarly when we add or remove a node we do not need
to shift the data up or down, we just link in an extra node or unlink a node.
Disadvantages are that to find a node with a given index we need to count through the
list until we get to that index. The linked list also uses more memory than a full array list.
CHAPTER 4. ABSTRACT DATA TYPES 11
data
Adding a new node at the start is slightly different. In this case there is no previous
node. We have to link our node using the start variable instead.
To remove a node from the middle we again need to locate the previous node. We
change the next field of the previous node to equal the next field of the node to remove.
@
@
start - next - next - next
@
R
@
Again the first node is a special case where we change the start variable.
Here is the code of a basic linked list implementation. We extend AbstractList to
provide the methods that are not implemented here.
package com328 . structure ;
import java . util . AbstractList ;
import java . util . Collection ;
import java . util . Iterator ;
/* * MyLinkedList
* @author C.T. Stretch
CHAPTER 4. ABSTRACT DATA TYPES 12
*/
p ub li c c l a s s MyLinkedList <E > extends AbstractList <E >
{
i n t size ;
Node <E > start ;
p ub li c E remove ( i n t index )
{
i f ( index == 0)
return removeFirst ();
i f ( index < 0 || index >= size )
throw new IndexOutOfBoundsException ("" + index );
Node <E > p = start ;
f o r ( i n t i = 0; i < index - 1; i ++)
p = p . next ;
E r = p . next . data ;
p . next = p . next . next ;
size - -;
return r ;
}
p ub li c E removeFirst ()
{
i f ( size == 0)
throw new IndexOutOfBoundsException ("0" );
E r = start . data ;
start = start . next ;
size - -;
return r ;
}
t . data = element ;
return r ;
}
p ub li c E get ( i n t index )
{
i f ( index < 0 || index >= size )
throw new IndexOutOfBoundsException ("" + index );
Node <E > t = start ;
f o r ( i n t i = 0; i < index ; i ++)
t = t . next ;
return t . data ;
}
p ub li c i n t size ()
{
return size ;
}
p ub li c boolean isEmpty ()
{
return size == 0;
}
p ub li c void clear ()
{
size = 0;
start = n u l l ;
}
Node ( E data )
{
t h i s . data = data ;
}
p ub l i c void remove ()
{
i f ( canRemove )
{
CHAPTER 4. ABSTRACT DATA TYPES 16
i f ( p == n u l l )
{ start = start . next ;
t= null ;
}
else
{ p . next = t . next ;
t=p;
}
canRemove = f a l s e ;
}
else
throw new IllegalStateException ();
}
p ub l i c boolean hasNext ()
{
i f ( t == n u l l )
return start != n u l l ;
else
return t . next != n u l l ;
}
p ub l i c E next ()
{
p=t;
i f ( t == n u l l )
t = start ;
else
t = t . next ;
canRemove = true ;
return t . data ;
}
}
}
It is possible to avoid the special cases that occur at the start of the list by adding an
extra header node at the start of each list. The header node does not contain data, but
means that all the nodes containg data have a previous node. In this case the code for
adding changes to:
p ub li c void add ( i n t index , E element )
{
i f ( index < 0 || index > size )
throw new IndexOutOfBoundsException ("" + index );
CHAPTER 4. ABSTRACT DATA TYPES 17
Node p = start ;
f o r ( i n t i = 0; i < index ; i ++)
p = p . next ;
p . next = new Node <E >( p . next , element );
size ++;
}
We will look at some of the code from the LinkedList class. The full code can be
downloaded from java.sun.com.
The links of the chain are represented by an inner class called Entry. The inner class
is static, which means it does not need an instance of the containing class, and can only
access static variables of the containing class.
Entry ( E element , Entry <E > next , Entry <E > previous )
{
t h i s . element = element ;
t h i s . next = next ;
t h i s . previous = previous ;
}
}
Adding items is done by calling a private method that adds a new entry before a given
entry. The modCount variable keeps a count of the number of times the list is modified.
/* * Stack
* @author C.T. Stretch
*/
p ub li c i n t e r f a c e Stack <E >
{
/* * Add an item to the top of the stack
* @param e The item to add .
*/
p ub li c void push ( E e );
/* * Remove the item from the top of the stack
* @return The item removed
* @throws NoSuchElementException if the stack is empty .
*/
p ub li c E pop ();
/* * Test if the stack is empty .
* @return true for an empty stack .
*/
p ub li c boolean isEmpty ();
/* * Returns the item on top without removing it .
* @return The item on top .
* @throws NoSuchElementException if the stack is empty .
*/
p ub li c E peek ();
/* * Clear the stack .
*/
p ub li c void clear ();
}
We use a stack if we want to store data for later processing and we want to process the
data in the reverse order.
As an example suppose we want to check a mathematical expression or Java code to
see if the brackets nest correctly.
For example (3 + 2 [5 {4 1}]) is correct but (3 + {5 4) 7} is wrong.
CHAPTER 4. ABSTRACT DATA TYPES 21
We can do this using a stack. We scan the string, when we meet an open bracket we
push it on the stack. When we meet a close bracket we pop an open bracket from the stack
and check it is of the matching type. In the second example when we meet the ) we pop a
{ from the stack and see they are not matched.
/* * BracketCheck
* @author C.T. Stretch
*/
p ub li c c l a s s BracketCheck
{
char close ;
i n t index ;
Stack implementation
Stacks are easy to implement using either an array or a singly linked list. The code can be
extracted from our list implementations.
For an array implementation it is better to push and pop from the end of the array, as
these items can be used without shifting the rest.
For a linked list we can push and pop from the start, as we can find these without
searching the list.
If we use these ends all the stack operations are O(1), they do not depend on the size
of the stack.
Here is an array implementation:
package com328 . structure ;
import java . util . NoSuchElementException ;
/* * ArrayStack
* @author C.T. Stretch
*/
p ub li c c l a s s ArrayStack <E > implements Stack <E >
{
f i n a l s t a t i c i n t FIRST_LENGTH = 16;
@SuppressWarnings (" unchecked ")
p r i v a t e E [] theArray = ( E [])new Object [ FIRST_LENGTH ];
p r i v a t e i n t size ;
p ub li c E pop ()
{
i f ( size ==0) throw new NoSuchElementException (" Empty stack " );
return theArray [ - - size ];
}
p ub li c boolean isEmpty ()
{
return size == 0;
}
p ub li c E peek ()
{
i f ( size ==0) throw new NoSuchElementException (" Empty stack " );
return theArray [ size -1];
}
p ub li c void clear ()
{
size =0;
}
}
4.3.2 Queues
A queue data structure is like a queue to be served. We can add items at the end and
remove them from the front. The first item to be removed is the first to be added (FIFO).
A minimum queue needs the three methods
/* * Queue
* @author C.T. Stretch
*/
p ub li c i n t e r f a c e Queue <E >
{
/* * Add an item to the rear of the queue .
* @param e The item to add
*/
void add ( E e );
/* * Get the item from the front of the queue without removing it .
* @return the front item .
* @throws NoSuchElementException if the queue is empty .
*/
E examine ();
Queue implementations
Queues are a bit harder to implement efficiently as we need to access both ends of a list.
We can use a doubly linked list, which has quick access to both ends of the list. We can
use a circular array. We use an array, but do not move the data when we remove an item
from the front. This means are queue will move up the array.
start start+size
? ?
6 6
To avoid wasting space we allow the front of the queue to wrap to the start. We can
calculate the position after the end of the array as (start + size)%length
(start+size)%length start
? ?
6 6
With either of these implementations our queue operations are all O(1).
Here is an array implementation:
/* * ArrayQueue
* @author C.T. Stretch
*/
p ub li c c l a s s ArrayQueue <E > implements Queue <E >
{
f i n a l s t a t i c i n t FIRST_LENGTH = 16;
@SuppressWarnings (" unchecked ")
CHAPTER 4. ABSTRACT DATA TYPES 27
p r i v a t e i n t place ( i n t i )
{
return ( start + i ) % length ;
}
p ub li c E remove ()
{
i f ( size ==0) throw new NoSuchElementException (" Empty queue " );
E e = theArray [ start ];
start = ( start + 1) % length ;
size - -;
return e ;
}
p ub li c boolean isEmpty ()
{
return size == 0;
}
p ub li c E examine ()
{
i f ( size ==0) throw new NoSuchElementException (" Empty queue " );
CHAPTER 4. ABSTRACT DATA TYPES 28
Sorted lists A list of comparable items that are always kept in order.
Priority queues Queues where an item has a priority and highest prioriy items are re-
moved first.
Dequeues Double ended queues, pronounced decks, items can be added or removed at
both ends.