Copyright 2017. Packt Publishing.
All rights reserved. May not be reproduced in any form without permission from the publisher, except fair uses permitted under U.S. or applicable copyright law.
Protocols – Abstract
Data Types
In the last chapter, we saw a few basic data structures and some algorithms to
manipulate them. However, sometimes we may want to hide the implementation
details of a data structure and only want to know how they interact with other
algorithms. We may want to specify a few operations that they must allow and forget
about how they are achieved. This is not very different from abstraction of a part of a
program in any large software application. For example, in Java, we create interfaces
that only define the methods of an object that its class must implement, and then
we use this interface type, being confident that they will be implemented properly.
We do not want to think about how an implementation class would provide their
implementation. Such interfaces of data structure are called abstract data types. To
put this another way, an abstract data type (ADT) is a description of what a data
structure should do for its user. It is a list of operations that any implementation
must support and the complete description of what these operations are supposed to
do. A few of these have very frequent usage and have names given to them. We will
discuss a few of these here.
In this chapter, you will learn about the following concepts:
•
The definition of some common ADTs and their operations
•
How to implement these ADTs using both simple arrays and the data
structures you learned in the last chapter
[ 41 ]
EBSCO Publishing : eBook Collection (EBSCOhost) - printed on 9/22/2020 6:31 PM via NORTH AMERICAN UNIVERSITY
AN: 1513360 ; Chawdhuri, Debasish Ray.; Java 9 Data Structures and Algorithms
Account: ns212936.main.ehost
Protocols – Abstract Data Types
Stack
A stack is a very commonly used ADT. It is so named because it resembles a stack
of plates used in a restaurant. In such a stack, a plate that has been washed and put
last would stay on top. This would be the first plate to be picked up when a plate is
needed. The plate that went in first would be at the bottom of the stack and would be
picked last. So, the last plate to be placed in the stack is the first plate to get out, we
can also call this last in first out (LIFO).
Similarly, a stack ADT has a protocol where the last value that is put in it must be
returned on the first attempt to get a value out, and the value that went in first must
come out last. The following figure will make it more clear:
The operation of putting a new value in a stack is called push, and the operation of
retrieving a value from a stack is called pop. The element that was pushed last must
be popped first. The operation that allows one to see what the next pop will return is
called peek. The peek operation returns the top element without modifying the stack.
We expect all stack implementations to have all operations implemented in the time
complexity of O(1). This is also part of our stack protocol.
The stack ADT has the following operations:
•
Push: This adds an element at the top of the stack
•
Pop: This removes the element at the top of the stack
•
Peek: This checks the next value to be popped
[ 42 ]
EBSCOhost - printed on 9/22/2020 6:31 PM via NORTH AMERICAN UNIVERSITY. All use subject to https://www.ebsco.com/terms-of-use
Chapter 3
Since we know that ADTs are to data structures what interfaces are to classes, we
will code an ADT as an interface. The following is our interface for a stack:
public interface Stack {
void push(E value);
E pop();
E peek();
}
Of course, we will not leave it at this. We will see how a stack can actually be
implemented. To this end, we will see both a fixed-sized stack using an array to store
it's data, and a growing stack using a linked list for storing data. We will start with
the first.
Fixed-sized stack using an array
A fixed-sized stack uses a pre-allocated array to store values, that is when this stack
has used up the entire array, it can no longer accept new values until the old ones
are popped. This is not very different from an actual stack of plates, which most
certainly has a maximum height that it can handle.
As always, we start with the basic structure of the class, as follows:
public class StackImplArray implements Stack {
We need an array to store the elements, and we need to remember where the top of
the stack is in that array. The top always marks the index of the element that will be
popped next. When there are no more elements to be popped, it is set to -1. Why -1?
Because this is the natural choice as it does not require any special handling when
the first element is inserted:
protected E[] array;
int top=-1;
public StackImplArray(int size){
array = (E[])new Object[size];
}
}
The push operation in a stack can be to simply put the value in the array right next
to the current top and then set the top to the new position, as illustrated in the
following code:
@Override
public void push(E value) {
[ 43 ]
EBSCOhost - printed on 9/22/2020 6:31 PM via NORTH AMERICAN UNIVERSITY. All use subject to https://www.ebsco.com/terms-of-use
Protocols – Abstract Data Types
We first check whether the stack is already full or the current top is equal to the
maximum index possible, like this:
if(top == array.length-1){
throw new NoSpaceException("No more space in stack");
}
Now, we set the top to the new position and put the value we need to store in there
as follows:
top++;
array[top] = value;
}
The exception we used is a custom exception for this purpose. The code of the
exception is simple as shown in the following code:
public class NoSpaceException extends RuntimeException{
public NoSpaceException(String message) {
super(message);
}
}
The pop operation is just the opposite. We need to first take the value of the current
top and then update the top to the new position, which is one less than the current
position, as shown in the following code:
@Override
public E pop() {
We first check whether the stack is already empty, in which case we return a special
value, null. This is shown in the following code:
if(top==-1){
return null;
}
Then we update the top and return the value at the current top as follows:
top--;
return array[top+1];
}
[ 44 ]
EBSCOhost - printed on 9/22/2020 6:31 PM via NORTH AMERICAN UNIVERSITY. All use subject to https://www.ebsco.com/terms-of-use
Chapter 3
The peek operation does not change the state of the stack, and hence is even simpler:
@Override
public E peek() {
Just like the pop operation, we return null if the stack is empty:
if(top==-1){
return null;
}
Otherwise, we return the top element, as follows:
return array[top];
}
It is in fact possible to have a stack without an upper limit backed up by an array.
What we really need to do is that whenever we run out of space, we can resize the
array. Array actually cannot be resized, so the operation would be to create a new
array with a higher size (maybe twice as much as the original size), and copy all the
old elements into this array. Since this involves copying all the n elements to the new
array one by one, the complexity of this operation is O(n).
Variable-sized stack using a linked list
The problem with an array-based implementation is that since arrays are fixed in size,
the stacks cannot grow beyond a fixed-size. To resolve this, we have to do what we did
to fix the same problem for an array, that is, use a linked list instead. We start such an
implementation with the following bare bone class. The linked list will store the values.
Instead of assigning a new linked list to it, we do so using an overridable method
getNewLinkedList(). This will be useful in the class that extends from this one:
public class StackImplLinkedList implements Stack {
protected LinkedList list = getNewLinkedList();
protected LinkedList getNewLinkedList(){
return new LinkedList();
}
}
[ 45 ]
EBSCOhost - printed on 9/22/2020 6:31 PM via NORTH AMERICAN UNIVERSITY. All use subject to https://www.ebsco.com/terms-of-use
Protocols – Abstract Data Types
To see which end of the linked list must be used as the top of the stack, we need
to remember that our stack protocol expects the operations to be O(1), so we must
choose an end that allows both insertion and removal in O(1) time. That end is of
course the front of the list as we saw in the last chapter. This makes the following
code for the push operation self-explanatory:
@Override
public void push(E value) {
list.appendFirst(value);
}
Note that this time, we did not check whether the stack is full because this
implementation of the stack is never full, it grows as it needs and the underlying
linked list takes care of that.
The pop operation, however, does need to check whether the stack is empty and
return null at that point. The following code for the pop operation is also quite
self-explanatory:
@Override
public E pop() {
if(list.getLength()==0){
return null;
}
E value = list.getFirst();
list.removeFirst();
return value;
}
The peek operation is, of course, the same, except it does not remove the top element:
@Override
public E peek() {
if(list.getLength()==0){
return null;
}
return list.getFirst();
}
This concludes our linked list-based implementation of a stack. In the next section,
we will check out another ADT called a queue.
[ 46 ]
EBSCOhost - printed on 9/22/2020 6:31 PM via NORTH AMERICAN UNIVERSITY. All use subject to https://www.ebsco.com/terms-of-use
Chapter 3
Queue
What is the opposite of a stack? This may be a weird question. However, a stack follows
LIFO, last in first out. The opposite of that is first-in-first-out (FIFO). So, in some sense,
a FIFO ADT can be considered as the opposite of a stack. This is not very different from
a queue of people waiting for a bus or at a doctor's clinic. The first person to show up
gets the first chance to get onto the bus or to get to see the doctor. The second person
gets the second chance. No wonder, such an abstract data type is called a queue.
Appending to the end of a queue is called enqueuing and removing from it is called
dequeuing. The contract is, of course, that the first value that is enqueued would be the
first to be dequeued. The following figure illustrates this operation:
The queue ADT has the following operations:
•
Enqueue: This adds an element at the back of the queue
•
Dequeue: This removes an element from the front of the queue
•
Peek: This checks the element that would be dequeued next
The queue will be represented by the following interface:
public interface Queue {
void enqueue(E value);
E dequeue();
E peek();
}
[ 47 ]
EBSCOhost - printed on 9/22/2020 6:31 PM via NORTH AMERICAN UNIVERSITY. All use subject to https://www.ebsco.com/terms-of-use
Protocols – Abstract Data Types
Fixed-sized queue using an array
Just like the stack, we have an array-based implementation of a queue. However,
since a queue receives new values and removes old values from opposite sides, the
body of the queue moves as it does. The following figure will illustrate this point:
[ 48 ]
EBSCOhost - printed on 9/22/2020 6:31 PM via NORTH AMERICAN UNIVERSITY. All use subject to https://www.ebsco.com/terms-of-use
Chapter 3
This means that after a sequence of a few such operations, the end of the queue will
reach the end of the array, and there will be space left at the beginning of the array.
At this point, we don't want to stop receiving new values as there is space left, so we
roll over to the beginning of the array. That is to say, we continue adding the new
values at the beginning of the array.
To do all these manipulations, we must have separate variables storing the indexes
of the beginning and the end of the queue. Also, since due to roll over, sometimes
the end is smaller than the beginning, we store the length separately to avoid
confusion. We start with the bare bone implementation of the class just as before. The
start represents the index of the element that would be dequeued next and the end
represents the position of the next value that would be enqueued. This is illustrated
in the following code:
public class QueueImplArray implements Queue{
protected E[] array;
protected int start=0;
protected int end=0;
protected int length=0;
public QueueImplArray(int size){
array = (E[]) new Object[size];
}
}
The enqueue operation does not change the start position. The new value is put at
the end position of the array and the end is incremented by one. The end, of course,
needs to be rolled over in case it goes beyond the maximum index of the array, as
shown in the following code:
@Override
public void enqueue(E value) {
if(length>=array.length){
throw new NoSpaceException("No more space to add an element");
}
array[end] = value;
The modulo operator will make sure that the index goes to the beginning of the array
when it hits the end of the array, as follows:
end = (end+1) % array.length;
length++;
}
[ 49 ]
EBSCOhost - printed on 9/22/2020 6:31 PM via NORTH AMERICAN UNIVERSITY. All use subject to https://www.ebsco.com/terms-of-use
Protocols – Abstract Data Types
The dequeue operation does not change the end position. We read from the start
index and then increment the start index with rollover, as follows:
@Override
public E dequeue() {
if(length
Purchase answer to see full
attachment