Lecture03 ListsStacksQueues Notes
Lecture03 ListsStacksQueues Notes
Reading materials Dale, Joyce, Weems: chapters 2, 4, 5, 6, (parts of 3 that talk about recursive processing of lists)
OpenDSA (https://opendsa-server.cs.vt.edu/ODSA/Books/Everything/html/): chapter 9
(Liang 10th: 10.2 (abstraction), 10.6 (stacks), Comprehensive: 22 )
Contents
1 Abstract Data Types 3
1.1 Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 List ADT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.1 Example: StringList ADT - Informal Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.2 Example: StringList ADT - Formal Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3 Stacks ADT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3.1 Example: CharStack ADT - Informal Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3.2 Example: CharStack ADT - Formal Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4 Queue ADT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4.1 Example: ProcessQueue ADT - Informal Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4.2 Example: PrintJobQueueADT - Formal Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3 Linked Structures 7
3.1 Review of Java Reference Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2 Linked Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2.1 Linking Nodes Together . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
5 Performance Analysis 20
5.1 Singly Linked List ⇔ Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
• informal specification - an English description that provides list of all available operations on the data with their inputs and
outputs;
• formal specification - a Java interface definition that later can be implemented by concrete classes.
• application level (or user, or client): using a class for which you only know the formal ADT specification;
• logical level: designing the ADT itself given a set of requirements specified by the non-programmer user (this may involve asking
questions);
• implementation level: writing code for a class that implements a given ADT.
1.1 Collection
A collection object is an object that holds other objects. Typical operations provided by collection classes are:
• insert,
• remove,
• iterate through the collection.
The StringList contains a (possibly empty) collection of objects of type String. The list supports the following operations:
insert This operation adds a String object, given as a parameter, to the list of strings.
remove This operation removes a String object, given as a parameter, from the list of strings. If given String object is not on the list, the
list content does not change.
3
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
clear This operation removes all objects from the list. The list becomes empty.
contains This operation determines if a String object, given as a parameter, is stored in the list or not. It returns true or false, accordingly.
indexOf This operation determines the index (or location) of a String object, given as a parameter. It returns -1 if the given String object
is not stored in the list. (The indeces do not imply any ordering of the objects in the list and may have different value depending
on the implementation of this ADT).
get This operation returns an object stored at the index/location given as a parameter.
size This operation determines the number of objects stored in the list.
toString This operation produces a meaningful String representation of the list.
The formal specification of the ADT is given by Java’s interface. This makes it language specific, but it is still general enough to be
adaptable by implementations in other programming languages.
1
2 public interface StringList {
3 void insert ( String item );
4 void remove ( String item );
5 void clear ( );
6 boolean contains ( String item );
7 int indexOf ( String item );
8 String get ( int index );
9 int size ( );
10 String toString ();
11 }
See the StringList.java file in the lecture05 source code for a documented version of the above interface.
See Java’s generic List<E> interface at docs.oracle.com/javase/8/docs/api/java/util/List.html for an example
of similar interface.
The CharStack contains a (possibly empty) collection of objects of type Character. The stack supports the following operations:
insert / push This operation adds a Character object, given as a parameter, to the top of the stack of characters.
remove / pop This operation removes and returns a Character object from the top of the stack of characters.
peek This operation returns a Character object from the top of the stack of characters.
toString This operation produces a meaningful String representation of the stack.
4
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
1
2 public interface CharStack {
3 public void push ( Character item ) ;
4 public Character pop () ;
5 public Character peek () ;
6 public String toString () ;
7}
See the CharStack.java file in lecture source code for a documented version of the above interface.
See http://docs.oracle.com/javase/8/docs/api/java/util/Stack.html for Java’s generic Stack<E> class.
1.4 Queue ADT
Queues are structures in which elements are added to one end (rear/back of a queue) and removed from the other end (front of a queue).
Queues are first in first out structures (FIFO).
The ProcessQueue contains a (possibly empty) collection of objects of type Process. Process is a user defined class representing a
process waiting to be scheduled to run on the core of a CPU of a computer. The queue supports the following operations:
insert / enqueue This operation adds a Process object, given as a parameter, to the end of the queue of processes.
remove / dequeue This operation removes and returns a Process object from the front of the queue of processes.
toString This operation produces a meaningful String representation of the stack.
1
2 public interface PrintJobQueue {
3 public void enqueue ( PrintJob item ) ;
4 public PrintJob dequeue () ;
5 public String toString () ;
6}
See the PrintJobQueue.java file in lecture source code for a documented version of the above interface.
See http://docs.oracle.com/javase/8/docs/api/java/util/Queue.html for Java’s generic Queue¡E¿ interface.
Notice how different methods are handled there when the queue is empty or full.
5
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
• So, how can we ensure that we always have enough room to add another item? After all the arrays in Java are fixed size, aren’t
they?
How big should the array be with an empty list? The quick answer is that it depends on the application. The array should start with
some non-zero size. The initial size can be provided by the user as an option to the constructor.
This has to happen without user intervention. Any method that adds elements to the List needs to verify if there is enough room. If there
isn’t, it needs to resize the array before performing the addition.
One extra element. This is a very bad choice! Copying of all the items from one array to another takes time. It should be avoided if
possible. Increasing the array size by 1 means that we have to do it often.
Fixed number of elements. This generally works well. The actual number of elements should depend on the application.
Doubling current size. This is often implemented and produces good results assuring that the copying of the data is done infrequently.
But for very large arrays, this may lead to a very large increase in memory usage.
Where should the top be? There is another issue that we need to resolve though: at which end of the array should we have the top of
the stack? This is the end at which we need to perform all push (addition), pop (removal) and peek operations. We have two options:
• index zero (or front) of the array - this is a very bad choice that leads to slow operations (do not ever use this!), why?
• index of the last element of the array - this leads to efficient implementation of the operations even though the top of the stack
ends up moving as the stack grows (kind of like in a stack of real things)
6
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
3 Linked Structures
result in a 32-bit block of memory being associated with the variable x and then the bits are used to represent the value of 5 (for
simplicity, I will just write the number 5 instead of its binary equivalent).
With reference type variables the memory allocation is different. When you create a reference variable it does not have any room
(bits/bytes in memory) for storing data. It only contains enough memory to store a reference or an address of memory location at which
the actual data is stored.
The following line of code creates a reference variable, unless it is followed by creation of an actual object, there is no room to store
data.
p
Person p;
room for a
memory address
In fact, you should make a habit of always assigning the null value to a reference variable that does not actually reference any valid data:
p
Person p = null;
null
The new operator is used to create the actual object and to assign its memory address to a reference variable.
And since we do not really care about the hexadecimal value of the memory address (nor do we really have a way of knowing it in Java),
you’ll see arrows used to show that a reference variable refers to or ”points to” a memory location that contains the actual object.
p
Person object
stored
7
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
object 1
object 3
object 2
object 5
Notice that each object has an extra ”box” with an arrow coming out of it. The linked structures are composed of special kind of objects,
usually referred to as nodes. Each node contains the actual data, but also a reference (possibly more than one) to another node. Linked
structures get connected through these references to the next node.
We want to connect several nodes together in order to create a linked structure. Assume that our node will store a String object as the
data. This means that each node contains data part (String object), and the reference to another node:
class StringNode {
private String data;
The table below shows the steps needed to create several nodes and link them together into a linked structure.
next instruction memory view
sn
null
current
null
StringNode sn = null; StringNode
current = null;
sn
null
current
null
sn = new StringNode();
sn
null
current
null
current = new StringNode();
sn
current
null
sn.setNext( current );
9
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
sn
current
null
current.setNext(
new StringNode());
sn
current
null
current = current.getNext();
sn
current
null
current.setNext(
new StringNode());
current = current.getNext();
10
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
The implementation details and algorithms assume that we have a collection of connected nodes (as shown in previous section) with a
reference to the first node called head.
We want to insert a new node into an existing list of nodes. The steps differ slightly depending on where the new node needs to be
inserted: front, back or middle of the list.
Inserting an element at the beginning Assume that we have a linked list with four nodes:
head
null
We want to add a new node at the beginning of this list, i.e., in front of the very first node.
head
null
This requires making the new node point to the current first element and then changing where the head reference points to.
head
null
11
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
head
null
WARNING: If you change where the head reference points to first, then the rest of the list will be lost!
These steps in code:
// create new node
newNode.setNext(head);
head = newNode;
Inserting an element at the end Again, assume that we start with a linked list with four nodes:
head
null
We want to add a new node at the end of this list, i.e., the current last node should point to our new node.
head
null
Since, we only have a reference to the first node, we need to find the last node (easy to find since it is the only one pointing to null). So
we have a new reference, called current that now points to the last node.
current
head
null
12
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
Our new node should point to null, since it will become the last node in the list,
current
head
null
null
and the current last node should point to the new node.
current
head
null
In this case, the order of the last two steps does not matter. (In fact, if the StringNode constructor automatically sets the next reference
to null that step can be omitted.)
These steps in code:
// create new node
StringNode newNode = new StringNode ( );
// create the c u r r e n t r e f e r e n c e and point it to the last node
StringNode current = head;
while (current.next != null )
current = current.getNext();
// make the new node point to null
newNode.setNext(null);
// make the last node point to the new last node
current.setNext (newNode);
Inserting an element in the middle The previous two scenarios covered special cases. Now, we have the same initial linked list, but
we want to add a node in the middle. At this point it does not matter how the actual location is selected, so let’s add it after the second
node.
head
null
At this point it does not matter how the actual location is selected, so let’s add it after the second node. We need the current reference
to point to the node that should be before the new node after the insertion (the exact details of how to advance the current reference from
head to that particular position depend on how the position is selected).
13
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
current
head
null
As with inserting at the front, we need to make sure not to disconnect the rest of the list during the insertion. The new node has to point
to its successor
current
head
null
head
null
14
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
As with the insertion, the removal of a node is slightly different depending on where the node is located in the list.
Removing an element at the beginning We start with our four-node linked list
head
null
and we want to remove the very first node (the one pointed to by the head reference).
head
null
This involves pointing the head reference to the second node. Once the first node is no longer pointed to by anything, it becomes
unreachable and memory used by it will be returned to available memory set by Java garbage collector.
head
null
Removing an element at the end We start with our four-node linked list
head
null
and we want to remove the very last node (the one that points to null). This means that we first need to find the node right before it ...
current
head
null
head
null
null
These steps in code:
// create a c u r r e n t r e f e r e n c e and a d v a n c e it to the one
15
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
Removing an element in the middle We start with our four-node linked list
head
null
and we want to remove a node in the middle (not the first one or the last one). We need the current reference to point to the node
before the one that needs to be removed (how this is done depends on the specifics of how the node to be deleted is selected).
current
head
null
The node pointed to be current needs to point to the node ”one away” in the list (the one that the node to be deleted is pointing to).
current
head
null
... / / d e p e n d s o n s p e c i f i c s o f t h e p r o b l e m
The clear() method of the list ADT empties the list. This can be accomplished by simply pointing the head reference of our list to
null.
16
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
The contains() method determines if a particular data item is in the list or not. With linked lists the data items are stored inside the nodes.
When implementing contains method we need to make sure that we are ”looking at” the actual data items, not at the nodes.
Here is an algorithm for searching in the linked list:
make current reference equal to head
while current != null
check if current.data is what we are after
if yes
return true
advance current to the next node (current = current.next )
if we reach this point we did not find what
we were after so return false
This method may be considered inappropriate for the linked list - after all we do not use indexes to access the elements. But since
the get method expects such index, we will provide it anyway. The index returned by this method does not provide any information
regarding ordering between the elements. In fact, at this point we are dealing with unsorted lists.
The implementation of this method is very similar to the implementation of contains method: we need to locate the element and
return its position rather than returning Boolean value indicating it was found or not.
Here is an algorithm for searching in the linked list and returning the position information:
The get method returns the data element of the list at a particular location, call it index. Since we only have a reference to the first
node, we need to jump over the first index-1 nodes and then return the data element in the indexth node.
Here is an algorithm for finding and returning indexth node in a linked list:
make current reference equal to head
set counter to zero
while current != null and counter != index
increment the counter by one
if counter == index
return current.data
otherwise (we reached the end of the list)
return null
17
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
The most efficient implementation of this method would make use of an auxiliary data field that keeps track of the number of elements
in the list: its value is adjusted on every deletion and insertion. The method then simply returns a value of this variable.
But as an exercise we will develop this method to compute a size of a list in two different ways: iteratively and recursively.
Iterative algorithm The details of an iterative algorithm were discussed in previous parts of this section. We need to move through
all the nodes incrementing a counter until we find a node that is pointing to null.
Here is an iterative algorithm for determining the size of a linked list:
make current reference equal to head
set counter to zero
while current != null
increment the counter by one
return the value of counter
Recursive algorithm We need to decide on the base case and the recursive step of the recursive algorithm. The smallest possible
linked list is an empty list. In that case the head points to null and the size is zero. This is the base case of the algorithm. If the head
reference does not point to null than we know that the size is at least 1 (since head points to an actual node) plus the size of the list that
the first node points to. This is the recursive step.
Here is a recursive algorithm for determining the size of a linked list:
size ( StringNode current )
if current == null
return 0;
else return 1 + size(current.next);
4.2 Stacks
In implementing linked structure based stacks we are going to reuse the code developed in the previous section. The stack class
implementation needs to implement the interface that we have been using previously (but this time it is written for String objects):
1 public interface StringStack {
2 public void push ( String item ) ;
3 public String pop () ;
4 public String peek () ;
5 public String toString () ;
6}
The quick and dirty solution is to define a stack class that has our LinkedStringList as a data field and calls its respective methods. This
approach reuses methods of the LinkedStingList class and wraps them in its own methods appropriate for a stack.
1
2 public class LinkedStringStack implements StringStack{
3
4 private LinkedStringList stack;
5
6 /* c o n s t r u c t o r needs to create a L i n k e d S t r i n g L i s t object */
7 public LinkedStringStac ( ) {
8 stack = new LinkedStringList ();
9 }
18
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
10
11 /* uses the i n s e r t B a c k () method of the list */
12 public void push ( String item ) {
13 if (item != null )
14 stack.insertBack( item ) ;
15 }
16
17 /* uses the r e m o v e B a c k () method of the list ,
18 but needs to store the r e m o v e d item to return it */
19 public String pop () {
20 String item = null;
21 if ( ! stack.isEmpty () ) {
22 item = stack.get( stack.size() - 1);
23 stack.removeBack() ;
24 }
25 return item;
26 }
27
28 /* this uses the get () method of the list */
29 public String peek () {
30 String item = null;
31 if ( ! stack.isEmpty () )
32 item = stack.get( stack.size() - 1);
33 return item;
34 }
35
36
37 public String toString () {
38 return stack.toString();
39 }
40 }
We can also implement the stack class with its own head reference, numOfElements variable and the methods
This approach avoids making duplicate method calls, but is still very inefficient: we need to traverse the entire list for every insertion,
deletion and get operation.
Since a stack data structure only requires accesses (push, pop, peek) on one end of the linked list, the top of a stack should be at the very
beginning of the list. This way every access takes only one step and there is never a reason to traverse the entire list.
An efficient linked list based stack implementation should have its own head reference, (optional) numOfElements variable and the
methods
• toString().
4.3 Queues
As with the stack, in implementing linked structure based queues we are going to reuse the code developed for the linked list. The queue
class implementation needs to implement the interface that we have been using previously (but this time it is written for String objects):
1
2 public interface StringQueue {
3 public void enqueue ( String item ) ;
4 public String dequeue () ;
5 public String toString () ;
6}
A queue requires accesses to both ends of the list, so on one of its operations the entire list has to be traversed. Assuming that the queue
is somewhat balanced (i.e. number of insertions is approximately the same as number of removals), it does not really matter which end
of the list is considered the front of the queue and which is considered the back of the queue.
A possible linked list based queue implementation would have its own head reference, (optional) numOfElements variable and the
methods
• toString().
We will return to a linked list based implementation of a queue when we discuss doubly linked lists. This will provide a more efficient
solution than the one above that uses singly linked lists.
5 Performance Analysis
20
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
It turns out that it is much simpler to write code for evaluation of arithmetic expressions when they are written in a format that lets us
ignore parenthesis and that does not depend on operator precedence. These two ways are prefix and postfix notations.
21
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
infix postfix
2+5 2 5 +
(2 + 4) × 5 2 4 + 5 ×
2+4×5 2 4 5 × +
((15/(7-(1 + 1))) × 3)-(2 + (1 + 1)) 15 7 1 1 + − / 3 × 2 1 1 + + −
22
CSCI-UA 102 Joanna Klukowska
Lecture 5: ADT: Lists, Stacks, Queues [email protected]
• Make sure that it is really the same node that the head is pointing to, NOT that it just contains the same data.
• Make sure that such node is found after traversing the whole list, NOT when the current is initialized to head reference.
The first node is pointed to by a head reference, the last node is pointed to by a tail reference. The first node has prev pointing to
null and the last node has next pointing to null.
Java’s LinkedList¡E¿ class http://docs.oracle.com/javase/8/docs/api/java/util/LinkedList.html provides
an implementation of a doubly linked list.
7.3 Deque
This strange name is short for double ended queue. It is a hybrid structure that allows insertions and removals at both ends of the queue.
Java has a Deque<E> interface http://docs.oracle.com/javase/8/docs/api/java/util/Deque.html and one
of its implementations if provided by Java’s LinkedList¡E¿ class http://docs.oracle.com/javase/7/docs/api/java/
util/LinkedList.html ( the same one that implements a doubly linked list).
23