The List, Stack, and Queue ADTs
Abstract data type (ADT):
• An ADT is a set of objects together with a set operations to
be performed on these objects.
• An ADT corresponds to (one or more) classes in an OO
language such as C++ and Java.
• There may be more than one implementation for the same
ADT, with different advantages in terms of efficiency, ease of
use, ease of implementation.
We will review the ADTs for lists, stacks, and queues, mainly
based on array or linked list implementations. We will also
study some typical applications of these ADTs to learn when
to use them.
The List ADT:
• A list of objects A1, A2, …, An, where n is the size of the list.
• Some typical operations include find, insert, remove,
findKth, printList, makeEmpty.
• An array implementation requires (n) time for insert and
remove operations in the worst case.
• A linked list implementation offers the flexibility of
dynamically growing the list and of O(1) time insert and
remove operations (assuming the position in the list is known).
Programming Details in Linked List Implementation:
• Use three classes for the List ADT:
(1) ListNode (for the individual nodes, or objects, in
(2) LinkedList (for the list object itself consisting of
the nodes); and
(3) LinkedListItr (for the current location in a
LinkedList object, in a particular application).
• Each class consists of one or more constructor functions.
• The ListNode class contains two member variables: element
for the “data” field and next for the link field lining to the
next node in the list.
Linked List Implementation (cont’d):
• The LinkedListItr class contains functions isPastEnd,
retrieve, advance; and contains a member variable current
for the current location in a LinkedList object.
• The LinkedList class may use a dummy header node to be
the first node in the list which somewhat simplifies the insert
function (thus, an “empty” list contains the header node as
the only node in the list). The functions included in the class
are find, remove, findPrevious, and insert.
Variations of linked list implementations include
• doubly linked lists – each node uses two link fields to link
to the two neighboring nodes in the list, respectively; and
• circular linked lists – the last node in the list is linked to
the front of the list, eliminating the header node.
Two Applications of Linked Lists:
(1) Polynomials (in a single variable x) – use nodes to
represent the non-zero terms arranged in decreasing order
of the exponents; typical operations include: constructor
(initialize the zero polynomial in constant time); add (in
O(m + n) time for two polynomials of m and n terms,
respectively); and multiply (in O(mn(min(m,n)) time for
two polynomials of m and n terms, respectively).
(2) Radix sort – sort n numbers in the range of [0..m–1],
where m bp for some constant p (of moderate size),
based on a multi-pass bucket sort technique, resulting in
O(p(n + b)) time using O(b+ n) space; this is a linear time
O(n) algorithm when p is a constant and b = O(n). For
example, m = 232 for 32-bit unsigned integers. Choose b =
211 = 2048, then m b3 and n numbers can be sorted in
O(3(n + 211) = O(n) time.
Algorithm for adding two polynomials in linked lists p, q:
set p, q to point to the two first nodes (no headers)
initialize a linked list r for a zero polynomial
while p != null and q != null
if p.exp > q.exp
create a node storing p.coeff and p.exp, then
insert at the end of list r; advance p
else if q.exp > p.exp
create a node storing q.coeff and q.exp, then
insert at the end of list r; advance q
else if p.exp == q.exp
if p.coeff + q.coeff != 0
create a node storing p.coeff + q.coeff
and p.exp, then insert at the end of list r
advance p, q
if p != null copy the remaining terms of p to end of r
else if q != null copy the remaining terms of q to end of r
Each iteration of the while loop takes O(1) time, moving at least
one of the two pointers p and q one position over. Any remaining
terms will be copied after the loop. Thus the total time is O(m+n)
because there are a total of m+n terms in the two polynomials.
Algorithm for multiplying two polynomials in linked lists p, q:
set p to the first node (no header) of one polynomial
initialize a linked list r for a zero polynomial
while p != null
copy list q to a new list t
multiply the term pointed by p to each term of t by
set t.exp += p.exp
set t.coeff *= p.coeff
add polynomial t to polynomial r (then discard t)
If polynomial p, q have m, n terms, respectively, the while loop
runs m iterations. In each iteration, producing the polynomial t
(copy and multiply) costs O(n) time since the length of t is O(n).
Adding polynomial t to r runs in time O(n + length of r). Since
the length of r in the beginningm of iteration k is O((k –1)n), so the
total time of the algorithm is O(k1(n (k 1)n)) O(m2n). The choice of m
and n is arbitrary, so we can achieve O(mn(min(m,n)) time.
An Example demonstrating Radix Sort:
Given n = 10 numbers: 64, 8, 216, 512, 27, 729, 0, 1, 343, 125.
Use b = 10 buckets and sort the numbers using the least
significant digits in the first pass (i.e., distribute the numbers
into10 buckets based on the last digits), then sort the list of
numbers resulting from the first pass using the next significant
digits; finally, sort the list (of pass two) using the most
10 buckets 0 1 2 3 4 5 6 7 8 9
list after first pass 000 001 512 343 064 125 216 027 008 729
list after second pass 001 216 027
000 512 125 343 064
list after the third pass 008
000 125 216 343 512 729
The Stack ADT:
• A stack is a list with two operations insert and delete, such that
the most recently inserted item is deleted first (hence the name
LIFO, for a last-in-first-out structure).
• A stack is typically pictured as a “stack of items” arranged
vertically where the insertion and deletion take place at the “top”
• The stack ADT supports operations isFull, isEmpty,
makeEmpty, push, pop, top.
Application of Stacks: Expressions from Infix to Postfix.
An infix expression is what we typically use in arithmetic (or
algebra), where an operator (+, –, *, etc.) is written between
the two neighboring operands (for binary operators). For
example, a + b, or a + b * c (multiply first before add due to
higher precedence), (a + b) * c (use parentheses to alter
precedence). As much as we are used to infix expressions,
they are harder for the computer to process and evaluate.
A postfix expression (also known as Polish notation since it
was invented by a Polish logician in the 1920s), uses a
different convention for writing arithmetic expressions, totally
doing away the parentheses and making the expressions more
efficient to evaluate by computers. The idea is to write each
operator after the two corresponding operands. For example,
ab+, abc*+, ab+c*, respectively, are the postfix expressions
corresponding to the preceding examples.
The idea of converting infix to postfix using a stack:
Example. Convert infix a + b * c to postfix abc*+.
We scan the input stream left to right, output each operand as
they are scanned. The main idea is that when encountering an
operator, it is pushed onto the stack if the stack is empty, or if it
has a higher precedence than that of the stack top. Thus, the
following chart demonstrates the snapshots of the input stream
vs. the stack and the output during the conversion process:
next token stack output comments
(Initially) empty none
a empty a always output operand
+ + a push when stack empty
b + ab always output operand
* +* ab push if higher precedence
c +* abc always output operand
(at end) empty abc*+ pop everything off stack
Another example: Convert infix a*(b + c)/d to postfix abc+*d/.
next token stack output comments
(Initially) empty none
a empty a output operand
* * a push operator if stack empty
( *( a always push (
b *( ab output operand
+ *(+ ab push operator if stack top (
c *(+ abc output operand
) * abc+ pop all operators until (
/ / abc+* pop *, push /
d / abc+*d output operand
(at end) empty abc+*d/ pop everything off stack
Note that the token ( is pushed onto stack when scanned; once it
is in the stack all operators are considered with a higher
precedence against (. Also, we need to resolve operators with
left or right-associative properties. For example, a+b+c means
(a+b)+c but a^b^c means a^(b^c).
We define the precedence levels of various operators including
the parentheses, distinguishing whether the operators are
currently inside the stack or they are incoming tokens.
operator tokens in-stack precedence in-coming precedence
) (N/A) (N/A)
^ 3 4
*, / 2 2
+, – 1 1
( 0 4
$ –1 (N/A)
The idea is that when encountering an in-coming operator, pop
all operators in the stack that have a higher or equal ISP than the
ICP of the new operator, then push the new operator onto the
stack. The initial stack is marked with a “bottom marker” $ with
a –1 precedence, which serves as a sentinel symbol.
Algorithm Converting Infix Expression E to Postfix Notation:
create a stack and push the bottom-marker $ onto stack
perform the following steps forever (until exit out of it)
set x = nextToken(E)
if x = end-of-input
pop all operators off the stack and output
(except the marker $)
exit out of the loop
else if x = operand, output x
else if x = ‘)’
pop all operators off stack and output each
until ‘(‘; pop ‘(‘ off but don’t output it
else pop all operators off stack as long as their
push x onto stack
Note that the time complexity of the algorithm is O(n), n = the
number of input tokens. This algorithm assumes no input errors.
The Queue ADT:
• A queue is a fist-in-first-out (FIFO) structure in which the
first (oldest) object inserted will be the first to be deleted.
• A linked list implementation is straightforward, with a
“pointer” front pointing to the fist (oldest) object and another
pointer back pointing to the most recent object in the list.
Insertions take place at the back of the list, deletions at the
front. Both operations can be done in O(1) time.
front After back
A circular (wrap-around) array implementation of queues:
When a fixed sized array is used to store the list of objects in a
queue, as objects come and leave, the portion of the array that
contains the current objects “drifts” towards the end of the
array, demonstrated in the following snapshots:
(drifting towards back)
A clever solution is let the array wraparound, so that its end
continues to the front.
A circular array implementation of queues (cont’d):
• The class uses 4 private instance variables: the array (of
some default size), currentSize (count of current objects),
front, and back (indexes of the first and last objects in the
array, respectively). Initially, front = currentSize = 0,
back = –1.
• Some public class methods include isEmpty(), isFull(),
makeEmpty(), getFront(), enqueue(), and dequeue().
• Queues are used for scheduler applications, as in
operating systems, event processing, and graph traversals.