CE204
Data Structures and Algorithms
Part 2
28/01/2019 CE204 Part 2
1
Abstract Data Types 1
An abstract data type is a type that may be specified completely without the use of any programming language.
To specify an abstract data type we need to provide:
• a name for the type
• the names of the primitive operations that can be performed on the type, and their argument and result types
• the semantics of the operations, i.e. a description of how they behave
28/01/2019 CE204 Part 2
2
Abstract Data Types 2
If an abstract data type (ADT) has been fully specified an application program should be able to make use of the type without knowing how it has been implemented. For example, a stack may be implemented using an array or a linked list (or other alternatives) but a programmer should know what the effect of a statement such as
push(7, myStack);
or
will be without needing to know which representation has been chosen.
myStack.push(7);
28/01/2019 CE204 Part 2
3
Abstract Data Types 3
To guarantee that an application will work even if the representation of the ADT is changed the application must access objects of the ADT only by means of the primitive operations.
If possible the implementer of the ADT should write his program in a manner that makes it impossible for an application to access objects other than by using the primitive operations; in Java we can do this by making the instance variables (i.e. the data in the object) private.
28/01/2019 CE204 Part 2
4
The Stack ADT 1
A stack is a last-in-first-out data store, which can only be accessed from the top.
The essential primitive operations for the stack ADT are • push, which adds an item to the top of the stack
• pop, which removes the top item
• top, which returns the top item.
In addition we need to provide an operation to create an empty stack and an operation isempty to determine whether a stack is empty.
28/01/2019 CE204 Part 2
5
The Stack ADT 2
The following axioms give a semantics to the operations
isempty(createstack()) = true isempty(push(n, s)) = false top(push(n, s)) = n pop(push(n, s)) = s
In these axioms s refers to the state of a stack before operations are applied, so, for example, the last axiom states that the result of applying pop to the result of pushing n onto s is that the stack will be back in its starting state.
28/01/2019 CE204 Part 2
6
The Stack ADT 3
Note that the axioms on the previous slide assume a procedural approach in which, for example, the push operation takes as arguments a data item and a stack and returns as its result the new state of the stack. In an object-oriented implementation push would be a one-argument method to be applied to a Stack object.
Also note that the axioms do not describe the behaviour in error situations such as an attempt to examine the top item in a empty stack.
28/01/2019 CE204 Part 2
7
Implementing Stacks in Java 1
There are many possible ways to implement the stack ADT in Java – since all should behave in the same way and support the same primitive operations we can write an interface that all approaches should implement.
public interface Stack
{ public boolean isEmpty();
public void push(T x); public void pop(); public T top();
}
28/01/2019 CE204 Part 2
8
Implementing Stacks in Java 2
Our interface did not specify an operation to create a new empty stack – implementations will simply use a constructor to ensure that all newly-created Stack objects are empty.
Exceptions should be thrown if attempts are made to apply pop or top to an empty stack. Hence we need a StackException class – this is presented on the next slide. We choose to extend RuntimeException so that throws clauses will not be needed.
28/01/2019 CE204 Part 2
9
Implementing Stacks in Java 3
public class StackException
extends RuntimeException
{ public StackException(String s)
{ super(“Attempted to apply ” + s +
} }
” to empty stack”);
28/01/2019 CE204 Part 2
10
Implementing Stacks Using Arrays 1
The simplest way to implement stacks is to store the elements in an array. To avoid having to shift elements when items are added or removed we choose to have the bottom of the stack at the beginning of the array, so it is necessary to keep track of the position of the top item, using an integer variable, usually referred to as a cursor.
An initial size for the array must be chosen; if the array becomes full it will be necessary to copy its contents into a new larger array and make the instance variable inside the stack object refer to this new array.
28/01/2019 CE204 Part 2
11
Implementing Stacks Using Arrays 2
A complication arises in the constructor for the stack – we need to initialise the array variable to refer to a new array of objects of type T but a statement such as
arr = new T[20];
is not allowed when T is a type parameter.
Hence it is necessary to create a new array of objects of type Object and then downcast it to type T[]. Normally we could not downcast in this way since an array of objects of type Object is not an array of objects of type T, but this kind of downcast is allowed when a new array of objects is created.
28/01/2019 CE204 Part 2
12
Implementing Stacks Using Arrays 3
public class ArrayStack
{ private T[] arr;
private int cursor;
public ArrayStack()
{ arr = (T[])new Object[20];
cursor = -1; }
public boolean isEmpty() { return (cursor==-1); }
// class continued on next slide
28/01/2019 CE204 Part 2
13
Implementing Stacks Using Arrays 4
// class ArrayStack
public T top()
{ if (cursor==-1)
throw new StackException(“top”); return arr[cursor];
}
public void pop() { if (cursor==-1)
throw new StackException(“pop”); arr[cursor] = null; // not essential cursor–;
}
// class continued on next slide
28/01/2019 CE204 Part 2
14
Implementing Stacks Using Arrays 5
// class ArrayStack
public void push(T x) { cursor++;
if (cursor==arr.length)
{ T[] temp = (T[])new Object[arr.length+20];
for (int i = 0; i
{ private ArrayList
public ALStack()
{ al = new ArrayList
public boolean isEmpty() { return (al.isEmpty()); }
public void push(T x)
{ al.add(x); // adds to end of list }
// class continued on next slide
28/01/2019 CE204 Part 2
18
Implementing Stacks Using Arrays 9
// class ALStack
public T top()
{ if (al.isEmpty())
throw new StackException(“top”); return al.get(al.size()-1);
}
public void pop()
{ if (al.isEmpty())
throw new StackException(“pop”);
al.remove(al.size()-1);
} }
28/01/2019 CE204 Part 2
19
Implementing Stacks Using Linked Lists 1
Array-based implementations of stacks suffer from the disadvantage of having to select an array size. If the array is too large memory space may be wasted, whereas if the array is too small performance may be affected by the need to copy the contents into a larger array.
These disadvantages can be overcome by using linked lists – since cells are created as the items are added the size of the list will be precisely the size of the stack.
We can choose to use the LinkedList class from the collections framework or create a linked list of ListCell objects as seen in part 1 of the slides.
28/01/2019 CE204 Part 2
20
Implementing Stacks Using Linked Lists 2
Since the LinkedList class has methods to add, remove and access items at both ends it does not matter which end of the linked list is the top of the stack when using the class to implement stacks.
On the following slides we present a version that uses the front of the list as the top of the stack – to use the back of the list we would simply use methods such as addLast instead of methods such as addFirst. Since the linked-list class uses doubly-linked lists the performance should not be affected.
28/01/2019 CE204 Part 2
21
Implementing Stacks Using Linked Lists 3
public class LLStack
{ private LinkedList
public LLStack()
{ ll = new LinkedList
public boolean isEmpty() { return (ll.isEmpty()); }
public void push(T x)
{ ll.addFirst(x); }
// class continued on next slide
28/01/2019 CE204 Part 2
22
Implementing Stacks Using Linked Lists 4
// class LLStack
public T top()
{ if (ll.isEmpty())
throw new StackException(“top”); return ll.getFirst();
}
public void pop()
{ if (ll.isEmpty())
throw new StackException(“pop”);
ll.removeFirst();
} }
28/01/2019 CE204 Part 2
23
Implementing Stacks Using Linked Lists 5
If we choose to implement stacks by building a singly-linked list from ListCell objects we should use the front of the list as the top of the stack in order to ensure that all of the operations can be implemented efficiently (since, if we choose to maintain a reference to the last item in the list, we cannot update this when removing the last item without traversing the list, and if we choose not to maintain such a reference, we cannot quickly access the last item).
The implementation using the front of the list as the top of the stack is easy and efficient so there is no benefit to be gained from attempting to implement our own doubly-linked lists.
28/01/2019 CE204 Part 2
24
Implementing Stacks Using Linked Lists 6
public class ListStack
{ private ListCell
// ListCell
public ListStack() { topCell = null; }
public boolean isEmpty() { return (topCell==null); }
public void push(T x)
{ topCell = new ListCell
// class continued on next slide
28/01/2019 CE204 Part 2
25
Implementing Stacks Using Linked Lists 7
// class ListStack
public T top()
{ if (topCell==null)
throw new StackException(“top”);
return topCell.data;
}
public void pop()
{ if (topCell==null)
throw new StackException(“pop”);
topCell = topCell.next;
} }
28/01/2019 CE204 Part 2
26
Decision Making: Which Implementation to Use 1
We have seen four different classes that implement the stack interface. In most cases the decision of which to use will not be very important. If there is a single stack and it is not expected to grow very large an array implementation may be chosen since a full array will require less memory space than a linked list containing the same items.
If however there are several stacks and it is not known which is likely to become large the unused space in arrays may result in inefficient memory usage so a linked-list implementation may be preferred.
28/01/2019 CE204 Part 2
27
Decision Making Which Implementation to Use 2
The use of classes from the collections interface will usually simplify the coding of an abstract data type, but in the case of a simple type such as a stack this is not very significant.
Direct implementations are likely to be a little more efficient since the overheads involved with calling library methods are avoided. Space may also be saved – in particular the use of the LinkedList class will require about 50% more space than the ListCell version since the library class is doubly-linked. This could be significant on a device with limited memory.
In languages without extensive libraries the use of direct implementations may be necessary, so it is useful to know how to produce them.
28/01/2019 CE204 Part 2
28
The Queue ADT 1
A queue is similar to a stack, but instead of adding and removing items at the same end items are added at the back and removed from the front.
The essential primitive operations for the queue ADT are • add, which adds an item to the back of the queue
• removefront, which removes the front item
• front, which returns the front item
• isempty, to determine whether a queue is empty
Unlike stacks there is no universal convention for the naming of the operations – for example, some authors use the names enqueue and dequeue for the addition and removal operations.
28/01/2019 CE204 Part 2
29
The Queue ADT 2
The following axioms give a semantics to the operations
isempty(createqueue()) = true
isempty(add(n, q)) = false
front(add(n, q)) = n, if q is empty
front(add(n, q)) = front(q), if q is not empty removefront(add(n, q)) = q, if q is empty removefront(add(n, q)) = add(n, removefront(q)),
if q is not empty
28/01/2019 CE204 Part 2
30
Implementing Queues in Java
As with stacks there are several possible ways to implement the queue ADT in Java; again we can write an interface that all approaches should implement.
public interface Queue
{ public boolean isEmpty();
public void add(T x); public void removeFront(); public T front();
}
28/01/2019 CE204 Part 2
31
Implementing Queues Using Arrays 1
We first consider an array-based implementation of the queue ADT. If the first element of the array holds the back item of the queue it would be necessary to shift elements each time an item is added. On the other hand, if the first element holds the front of the queue it would be necessary to shift elements each time the front item is removed.
To avoid any need for shifting the normal approach is to let the queue move along the array. Hence we need two cursors, frontPos and backPos to keep track of the positions of both the front and back items.
28/01/2019 CE204 Part 2
32
Implementing Queues Using Arrays 2
Observe that for a queue with just one element we would have frontPos==backPos. Removing an item from a queue causes frontPos to be incremented so after removing the element from a queue with only one element we will have frontPos==backPos+1. Hence this is the condition that indicates an empty queue.
Adding an element to a queue causes backPos to be incremented; after adding the first element to a newly-created queue we expect both frontPos and backPos to be 0 so the initial values given to these variables in the constructor should be 0 and –1.
28/01/2019 CE204 Part 2
33
Implementing Queues Using Arrays 3
public class ArrayQueue
{ private T[] arr;
private int frontPos, backPos;
public ArrayQueue()
{ arr = (T[])new Object[20];
frontPos = 0;
backPos = -1;
}
public boolean isEmpty() // first attempt { return frontPos==backPos+1;
}
// more methods needed
}
28/01/2019 CE204 Part 2
34
Implementing Queues Using Arrays 4
After a series of insertions and deletions the movement of the queue means that the end of the array would soon be reached. We could shift the contents of the queue back to the start of the array when this happens, but it is more efficient to treat the array as if it were circular and reuse location 0. Hence when incrementing and comparing the cursors we need to perform the operations modulo the size of the array, using the % operator. (Recall that a%b gives the remainder when a is divided by b.)
28/01/2019 CE204 Part 2
35
Implementing Queues Using Arrays 5
// some methods for ArrayQueue
public boolean isEmpty()
{ return frontPos==(backPos+1)%arr.length; }
public T front()
{ if (frontPos==(backPos+1)%arr.length)
throw new QueueException(“front”); return arr[frontPos];
}
public void removeFront()
{ if (frontPos==(backPos+1)%arr.length)
throw new QueueException(“removeFront”); frontPos = (frontPos+1)%arr.length;
}
28/01/2019 CE204 Part 2
36
Implementing Queues Using Arrays 6
If the array were to become full backPos+1 would be equal to frontPos (modulo the size of the array); this is the same as the condition for an empty queue, so there would be no way to distinguish between a full array and an empty queue. It would be possible to store the number of items in the queue in an extra instance variable, but a better approach is to limit the number of items that can be stored in an array to one less than the size of the array, and copy the contents of the queue into a larger array as soon as the array becomes full instead of waiting until an attempt is made to add another element. Hence the array will never be full so if frontPos==(backPos+1)%arr.length can only indicate an empty queue.
The coding of the add method is left as an exercise. 28/01/2019 CE204 Part 2
37
Implementing Queues Using Arrays 7
The ArrayList class does not provide support for the circular view of arrays needed for an efficient array implementation of queues, so any attempt to provide an ArrayList implementation would require more complicated coding than the array implementation and provide no benefits. Hence such an approach should not be considered.
28/01/2019 CE204 Part 2
38
Implementing Queues Using Linked Lists 1
A LinkedList version of the queue class is easy to implement and is very similar to the linked list version of the stack class.
To avoid confusion it is preferable to use the front of the linked list as the front of the queue, but as with the stack class it would be just as simple to use the front of the list as the back of the queue.
The complete code for the version using the front of the list as the front of the queue is provided on the following slides.
28/01/2019 CE204 Part 2
39
Implementing Queues Using Linked Lists 2
public class LLQueue
{ private LinkedList
public LLQueue()
{ ll = new LinkedList
public boolean isEmpty() { return (ll.isEmpty()); }
public void add(T x) { ll.addLast(x);
}
// class continued on next slide
28/01/2019 CE204 Part 2
40
Implementing Queues Using Linked Lists 3
// class LLQueue
public T front()
{ if (ll.isEmpty())
throw new QueueException(“front”);
return ll.getFirst();
}
public void removeFront()
{ if (ll.isEmpty())
throw new QueueException(“removeFront”); ll.removeFirst();
} }
28/01/2019 CE204 Part 2
41
Implementing Queues Using Linked Lists 4
As with implementations of stacks, space can be saved and a slight efficiency gain can be achieved by providing a direct implementation using singly-linked lists.
In order to be able to implement the removeFront method efficiently we need to be able to access a reference to the second item in the queue, so there must be a link from the first item to the second item. Hence the front of the queue needs to be at the head of the list.
To efficiently implement the add method we need to access the last item in the linked list, so we need to maintain a reference to this as well as the reference to the first item in the list.
28/01/2019 CE204 Part 2
42
Implementing Queues Using Linked Lists 5
Since items will be added to the back of the linked list the next reference of any newly-created list cell will always be null. Hence the constructor for the cell class should take only one argument, the value to be stored, and always initialise the next reference to null.
We could add a new one-argument constructor to the ListCell class previously presented, but it is just as simple to write a new class with only a one-argument constructor. The implementation on the following slides uses such a class, called QCell.
28/01/2019 CE204 Part 2
43
Implementing Queues Using Linked Lists 6
// ListQueue.java
class QCell
{ T data;
QCell
QCell(T x)
{ data = x;
next = null; }
}
// file continued on next slide
28/01/2019 CE204 Part 2
44
Implementing Queues Using Linked Lists 7
// ListQueue.java continued
public class ListQueue
{ private QCell
public ListQueue()
{ frontCell = backCell = null; }
public boolean isEmpty()
{ return (frontCell==null); }
// class continued on next slide
28/01/2019 CE204 Part 2
45
Implementing Queues Using Linked Lists 8
// class ListQueue
public T front()
{ if (frontCell==null)
throw new QueueException(“front”); return frontCell.data;
}
public void removeFront() { if (frontCell==null)
throw new QueueException(“removeFront”); frontCell = frontCell.next;
if (frontCell==null) backCell = null;
}
// class continued on next slide
28/01/2019 CE204 Part 2
46
Implementing Queues Using Linked Lists 9
// class ListQueue
public void add(T x)
{ QCell
if (frontCell==null)
frontCell = backCell = newCell;
else
{ // order of these statements is important!
backCell.next = newCell;
backCell = newCell;
}
} }
28/01/2019 CE204 Part 2
47