Title: Java Software Structures
1Java Software Structures
2Chapter Objectives
- Examine queue processing
- Define a queue abstract data type
- Demonstrate how a queue can be used to solve
problems - Examine various queue implementations
- Compare queue implementations
3Queues
- A queue is a linear collection whose elements are
added on one end and removed from the other - First-In, First-Out (FIFO)
- Elements are removed from a queue in the same
order in which they are placed on the queue
4Queues
5Operations on a Queue
- Enqueue - Adds an element to the rear of the
queue. - Dequeue - Removes an element from the front of
the queue. - First - Examines the element at the front of the
queue. - isEmpty - Determines if the queue is empty.
- Size - Determines the number of elements on the
queue
6Operations on a Queue
- Sometimes enqueue is simply called add or insert
- Dequeue is sometimes called remove or serve
- The first operation is sometimes called front
- Enqueue, dequeue, and first correspond to the
stack operations push, pop, and peek - There are no operations that allow the user to
reach into the middle of a queue
7A Queue ADT
- //
- // QueueADT.java Authors Lewis/Chase
- //
- // Defines the interface to a queue collection.
- //
- package jss2
- import java.util.Iterator
- public interface QueueADT
-
- // Adds one element to the rear of the queue
- public void enqueue (Object element)
- // Removes and returns the element at the
front of the queue - public Object dequeue()
- // Returns without removing the element at
the front of the queue - public Object first()
-
- // Returns true if the queue contains no
elements - public boolean isEmpty()
- // Returns the number of elements in the
queue
8A Queue ADT
9Using Queues - Codes
- A Caesar cipher is a simple approach to encoding
messages by shifting each letter in a message
along the alphabet by a constant amount k - Therefore, if k equals 3, the encoded message
- vlpsolflwb iroorzv frpsohalwb
- would be decoded into
- simplicity follows complexity
10Using Queues Codes
- An improvement can be made to this encoding
technique if we use a repeating key - Instead of shifting each character by a constant
amount, we can shift each character by a
different amount using a list of key values - For example, if the key values are
- 3 1 7 4 2 5
- then the first character is shifted by three, the
second character by one, the third character by
seven, etc
11Using Queues - Codes
12Using Queues - Codes
- The program Codes.java uses a repeating key to
encode and decode a message - The key of integer values is stored in a queue
- After using a key value, it is put back on the
end of the queue so that the key continually
repeats as needed for long messages
13Using Queues - Codes
- Program Output
- Encoded Message
- Fxi(gvyludix,z\zqmvimqm?p(Xrnmit?gyrr\v
mom?pyzv(Xgtp6 - Decoded Message
- All programmers are playwrights and all computers
are lousy actors.
14Using Queues - Codes
- This program actually uses two copies of the key
stored in two separate queues - The idea is that the person encoding the message
has one copy of the key, and the person decoding
the message has another - Also, note that this program doesnt bother to
wrap around the end of the alphabet - Using a queue to store the key makes it easy to
repeat the key by putting each key value back
onto the queue as soon as it is used
15Using Queues Ticket Counter
- Consider the situation in which you are waiting
in line to purchase tickets at a movie theatre - In general, the more cashiers there are, the
faster the line moves - The theatre manager wants to keep his customers
happy, but doesnt want to employ any more
cashiers than he has to
16Using Queues Ticket Counter
- Our simulated ticket counter will use the
following assumptions - There is only one line and it is first come first
served (a queue), - Customers arrive on average every 15 seconds,
- If there is a cashier available, processing
begins immediately upon arrival, - Processing a customer request and getting them on
their way takes on average 2 minutes (120
seconds) from the time they reach a cashier.
17Using Queues Ticket Counter
- First we can create a Customer class
- A Customer object keeps track of the time the
customer arrives and the time the customer
departs after purchasing a ticket - The total time spent by the customer is therefore
the departure time minus the arrival time
18Using Queues Ticket Counter
- Our simulation will create a queue of customers,
then see how long it takes to process those
customers if there is only one cashier - Then well process the same queue of customers
with two cashiers - Well continue this process for up to ten
cashiers - At the end well compare the average time it
takes to process a customer
19Using Queues Ticket Counter
- The program TicketCounter.java performs our
simulation - The outer loop determines how many cashiers are
used in each pass of the simulation - For each pass, the customers are taken from the
queue in turn and processed by a cashier - The total elapsed time is tracked, and at the end
of each pass the average time is computed
20Using Queues Ticket Counter
- Note that with eight cashiers, the customers do
not wait at all - Increasing the number of cashiers to 9 or 10 or
more will not improve the situation - Since the manager has decided he wants to keep
the total average time to less than seven minutes
(420 seconds), the simulation tells him that he
should have six cashiers
21Using Queues Radix Sort
- Another interesting application of queues is the
concept of a radix sort - Radix sort was not covered in Chapter 5 for two
reasons. - First, radix sort is not a comparison sort and
thus has very little in common with the
techniques that we discussed in Chapter 5. - Second, to implement it effectively, a radix sort
requires the use of a queue (several of them in
fact) or similar collection
22Using Queues Radix Sort
- Recall that a sort is based on some particular
value, called the sort key - A radix sort is based on the structure of the
sort key - Separate queues are created for each possible
value of each digit or character of the sort key - The number of queues, or the number of possible
values, is called the radix - For example, if we were sorting strings made up
of lowercase alphabetic characters, the radix
would be 26
23Using Queues Radix Sort
- Lets look at an example that uses a radix sort
to put ten three-digit numbers in order - To keep things manageable, well restrict the
digits of these numbers to 0 though 5, which
means well only need six queues - Each three-digit number to be sorted has a 1s
position (right digit), a 10s position (middle
digit), and a 100s position (left digit)
24Using Queues Radix Sort
- On the first pass, each number is put on the
queue corresponding to its 1s digit - On the second pass, each number is put on the
queue corresponding to its 10s digit - On the third pass, each number is put on the
queue corresponding to its 100s digit
25Using Queues Radix Sort
- Originally, the numbers are loaded into the
queues from the original list - On the second pass, the numbers are taken from
the queues in a particular order - They are retrieved from the digit 0 queue first,
and then the digit 1 queue, etc - Likewise, on the third pass, the numbers are
again taken from the queues in the same way - When the numbers are pulled off of the queues
after the third pass, they will be completely
sorted
26Using Queues Radix Sort
27Using Queues Radix Sort
- The program RadixSort.java implements the radix
sort - For this example, we will sort four-digit
numbers, and we wont restrict the digits used in
those numbers - Using an array of ten queue objects (one for each
digit 0 through 9), this method carries out the
processing steps of a radix sort
28Implementing Queues with Links
- Since it is a linear data structure, we can
implement a queue collection as a linked list of
LinearNode objects, as we did with stacks - The primary difference is that we will have to
operate on both ends of the list - Therefore, in addition to a reference pointing to
the first element in list (called front), we will
also keep track of a second reference that points
to the rear element on the list (called rear).
29Implementing Queues with Links
30The Enqueue operation
- The enqueue operation requires that we put the
new element on the rear of the list - In the general case, that means setting the next
reference of the current last element to the new
one, and resetting the rear reference to the new
last element - However, if the queue is currently empty, the
front reference must also be set to the new (and
only) element
31The Enqueue operation
//---------------------------------------------
-------------------- // Adds the specified
element to the rear of the queue.
//------------------------------------------------
----------------- public void enqueue (Object
element) LinearNode node new
LinearNode(element) if (isEmpty())
front node else rear.setNext
(node) rear node count
32The Enqueue operation
33The Dequeue operation
- The first issue to address when implementing the
dequeue operation is to ensure that there is at
least one element to return - If not, an EmptyCollectionException is thrown
- If there is at least one element in the queue,
the first one in the list is returned and the
front reference is updated
34The Dequeue operation
//---------------------------------------------
-------------------- // Removes the element
at the front of the queue and returns a //
reference to it. Throws an EmptyCollectionExceptio
n if the // queue is empty.
//------------------------------------------------
----------------- public Object dequeue()
throws EmptyCollectionException if
(isEmpty()) throw new EmptyCollectionExce
ption ("queue") Object result
front.getElement() front
front.getNext() count-- if
(isEmpty()) rear null return
result
35The Dequeue operation
36Other operations
- The remaining operations in the linked queue
implementation are fairly straightforward and are
similar to how they were handled for the stack
and bag collections - These operations are left as programming projects
37Implementing Queues with Arrays
- One array-based strategy for implementing a queue
is to fix one end of queue (say, the front) at
index 0 of the array - The elements are then stored contiguously in the
array
38Implementing Queues with Arrays
39The Enqueue operation
- The enqueue operation adds a new element to the
rear of the queue - As long as there is room in the array to for an
additional element, it can be stored in the
location indicated by the integer rear - The technique for expanding the capacity of the
queue array is the same as the one used for other
collections
40The Enqueue operation
//---------------------------------------------
-------------------- // Adds the specified
element to the rear of the queue, expanding //
the capacity of the queue array if necessary.
//------------------------------------------------
----------------- public void enqueue (Object
element) if (size() queue.length)
expandCapacity() queuerear
element rear
41The Enqueue operation
42The Dequeue operation
- the dequeue operation must assure that after
removing the first element of the queue, the new
first element (currently the second element in
the list) is stored at index 0 of the array - because we store the elements contiguously, we
cannot have gaps in the list - Therefore all elements must be shifted down one
cell in the array
43The Dequeue operation
- //------------------------------------------------
----------------- - // Removes the element at the front of the
queue and returns a - // reference to it. Throws an
EmptyCollectionException if the - // queue is empty.
- //---------------------------------------------
-------------------- - public Object dequeue() throws
EmptyCollectionException -
- if (isEmpty())
- throw new EmptyCollectionException
("queue") - Object result queue0
- // shift the elements
- for (int scan0 scan
- queuescan queuescan1
- rear--
- queuerear null
-
- return result
-
44The Dequeue operation
45Other operations
- The implementation of the first, isEmpty, size,
iterator, and toString operations using this
strategy are left as programming projects
46Implementing Queues with Circular Arrays
- The main problem with the array-based strategy
discussed in the previous section is that the
front end of the queue is fixed at index 0 - Therefore, every time a dequeue operation is
performed, all elements stored in the queue array
had to be shifted - If the queue is large, or if many dequeue
operations are performed, this creates a lot of
time-consuming shift operations.
47Implementing Queues with Circular Arrays
- Fixing the rear of the queue at index 0 does not
solve the problem - That change would simply require the element
shifting to occur in the enqueue method (before
the element is added) rather than in the dequeue
method (after the element is removed)
48Implementing Queues with Circular Arrays
- The key is not to fix either end
- As elements are dequeued, the front of the queue
will move further into the array - As elements are enqueued, the rear of the queue
will also move further into the array - The challenge comes when the rear of the queue
reaches the end of the array - Enlarging the array at this point is not a
practical solution, and does not make use of the
now empty space in the lower indexes of the array
49Implementing Queues with Circular Arrays
- To make this solution work, we will use a
circular array to implement the queue - A circular array is not a new construct it is
just a way to think about the array used to store
the collection - Conceptually, the array is used as a circle,
whose last index is followed by the first index
50Implementing Queues with Circular Arrays
51Implementing Queues with Circular Arrays
- Two integer values are used to represent the
front and rear of the queue - The value of front represents the location where
the first element in the queue is stored - The value of rear represents the next available
slot in the array (not where the last element is
stored)
52Implementing Queues with Circular Arrays
- The value of rear no longer represents the number
of elements in the queue - We will use a separate integer value to keep a
count of the elements - When the rear of the queue reaches the end of the
list, it wraps around to the front of the array
53Implementing Queues with Circular Arrays
54(No Transcript)
55Implementing Queues with Circular Arrays
- In general, after an element is enqueued, the
value of rear is incremented - But when an enqueue operation fills the last cell
of the array (at the largest index), the value of
rear must be set to 0 - Likewise, after an element is dequeued, the value
of front is incremented - After removing the element at the largest index,
the value of front must be set to 0 instead of
being incremented
56Implementing Queues with Circular Arrays
- The appropriate update to the values of rear and
front can be accomplished in one calculation
using the remainder operator () - Therefore, if queue is the name of the array
storing the queue, the following line of code
will update the value of rear appropriately - rear (rear1) queue.length
57Implementing Queues with Circular Arrays
- All of the operations for the circular array
implementation of a queue are left as exercises.
58Analysis of Queue Implementations
- There is a space complexity difference between
the algorithms - The linked implementation requires more space per
node since it has to store both the object and
the link to the next object - However, it only allocates space as it needs it
and then can store as many elements as needed up
to the limitations of the hardware - The array implementation does not require the
additional space per element for the pointer - However, typically array implementations allocate
more space than is required and thus may be
wasteful
59Analysis of Queue Implementations - Enqueue
- The enqueue operation for the linked
implementation consists of the following steps - create a new node with the element pointer
pointing to the object to be added to the queue
and with the next pointer set to null, - set the next pointer of the current node at the
rear of the queue to point to the new object, - set the rear pointer to point to the new object,
and - increment the count of elements in the queue.
60Analysis of Queue Implementations - Enqueue
- Each of these steps is O(1) and thus the
operation is O(1) - The analysis of the array implementation and the
circular array implementation also results in O(1)
61Analysis of Queue Implementations Dequeue
- The dequeue operation for the linked
implementation consists of the following steps - check to make sure the queue is not empty (throw
an exception if it is), - set a temporary pointer equal to the element
pointed to by the element pointer of the node
pointed to by the front pointer, - set the front pointer equal to the next pointer
of the node at the head of the queue, - decrement the count of elements in the queue, and
- return the element pointed to by the temporary
pointer.
62Analysis of Queue Implementations Dequeue
- As with our previous examples, each of these
operations consists of a single comparison or a
simple assignment and is therefore O(1). - Thus the dequeue operation for the linked
implementation is O(1)
63Analysis of Queue Implementations Dequeue
- The dequeue operation for the non-circular array
implementation consists of the following steps - Check to make sure the queue is not empty (throw
an exception if it is), - Set a temporary object equal to the first element
in the array, - Shift all of the elements in the array one
position to the left, - Decrement the rear and the count, and
- Return the temporary object.
64Analysis of Queue Implementations Dequeue
- All of these steps are O(1) with the exception of
shifting all of the remaining elements in the
array to the left which is O(n) - Thus the dequeue operation for the non-circular
array implementation has time complexity O(n)
65Analysis of Queue Implementations Dequeue
- The dequeue operation for the circular array
implementation consists of the following steps - Check to make sure the queue is not empty (throw
an exception if it is), - Set a temporary object equal to the object in the
front of the queue, - Set position front of the array to null,
- Decrement the count, and
- Return the temporary object.
- All of these steps are O(1) resulting in the
dequeue operation for the circular array
implementation being O(1).
66Analysis of Queue Implementations Other
Operation
- The front, isEmpty, and size operations are all
O(1)
67Summary of Key Concepts
- Queue elements are processed in a FIFO manner
the first element in is the first element out. - A queue is a convenient collection for storing a
repeating code key. - Simulations are often implemented using queues to
represent waiting lines. - A radix sort is inherently based on queue
processing. - A linked implementation of a queue is facilitated
by references to the first and last elements of
the linked list.
68Summary of Key Concepts
- The enqueue and dequeue operations work on
opposite ends of the collection. - Because queue operations modify both ends of the
collection, fixing one end at index 0 requires
that elements be shifted. - Treating arrays as circular eliminates the need
to shift elements in an array queue
implementation. - The shifting of elements in a non-circular array
implementation creates an O(n) complexity.