Introduction (CB chap. 1 & 2)

Download Report

Transcript Introduction (CB chap. 1 & 2)

Queues
Ellen Walker
CPSC 201 Data Structures
Hiram College
Queue
• Process data in a “fair” way regarding wait
time
• Access only “least recently added” item
– When “least recently added” item is removed, the
former 2nd least recently added item becomes
available!
• Why is it called a queue?
– Queue of people waiting for a bus
– Queue of jobs waiting for a printer
Operations on a Queue
•
•
•
•
Create an empty queue
Is the queue empty? (isEmpty)
Add a new item to the queue. (offer)
Remove the item that was least recently
added (remove or poll)
• Retrieve the item that was least recently
added (peek or element)
Removing & Retrieving Front
Element
• Removing front element
– poll() - returns null if queue is empty
– remove() - throws exception if queue is empty
• Retrieving front element without removing
– peek() - returns null if queue is empty
– element() - throws exception if queue is empty
Specification for a Queue Interface
Example
•
•
•
•
•
•
•
Create an empty queue
Offer “A”
Offer “B”
Remove (returns A)
Offer “C”
Remove (returns B)
Peek (returns C)
LIFO vs. FIFO
• A queue is FIFO (first in, first out)
– Queues are fair when someone has to wait
• A stack is a LIFO (last in, first out) data
structure
– New addition makes older items inaccessible
– Stacks model “interruptions” in the real world
(including getting back to what was interrupted…)
Reading and Printing a Line
//A queue of characters
Queue<Character> q = new LinkedList<Character>();
//Read a line (scIn is a Scanner for System.in)
String theLine = scIn.nextLine();
for(int c=0;c<theLine.length();c++)
q.offer(theLine.charAt(c));
//Print the characters
while(!q.isEmpty()){
System.out.println (q.remove());
}
Check a Palindrome with Queue and
Stack
boolean checkPalindrome(String theLine){
Stack<Character> s = new ArrayStack<Character>();
Queue <Character> q = new LinkedList<Character>();
//Put each character into both queue and stack
for(int c=0;c<theLine.length();c++){
char ch = theLine.charAt(c);
if(Character.isLetter(ch)){ //ignore space & punctuation
s.push(Character.toLowerCase(ch)); //ignore case
q.offer(Character.toLowerCase(ch));
}
}
Checking a Palindrome (continued)
//Check that forward (queue) and reverse (stack)
//sequence of characters is the same
while((!s.empty() && !q.isEmpty()){
char left = q.remove();
char right = s.pop();
if(left != right) return false;
}
return true; //both stack & queue are guaranteed
same size
}
Implementing Queues
• Start with a representation of a list
– Linked or array?
• Need access to front (for removing only)
• Need access to back (for inserting only)
Java’s List Implementation
• Java implements queue as a doubly-linked
list (LinkedList class)
• Doesn’t matter which end is which, but Java
uses head as front, tail as rear
• Allowing LinkedList to implement queue
breaks the abstraction
– Queue shouldn’t have iterators, size(), etc.
Single Linked Implementation
• Where should the front be? (easy to access
& remove, inserting not necessary)
• Where should the back be? (easy to insert,
direct access and removal not necessary)
• No real reason for either front or back to be
anywhere but beginning or end of list.
Solution
• Since we need to update the item *before*
the given item to delete (except special case
at head of list), we should delete at the head.
• Therefore, list head = queue front
• List tail = queue back, but that’s ok because
we’ll have the item before (old tail) for the
insertion.
Example (List Implementation)
• Empty queue
• Enqueue ‘a’, Enqueue ‘b’, Enqueue ‘c’
a
b
c
• Dequeue, Dequeue
c
• Enqueue ‘z’
c
z
Class Definition (data fields)
Public class ListQueue<E>
extends AbstractQueue<E>
implements Queue<E> {
private Node<E> front;
private Node<E> rear;
//Insert inner Node class from LinkedList here
private int size; // not strictly necessary
…
};
isEmpty & peek
•
//Is the Queue empty?
public boolean IsEmpty() {
return (front == null);
}
•
//Get item from front
public E peek() {
if (isEmpty()) return null;
else return front.data;
}
Offer
• // offer (add to end of list)
public boolean offer(E item){
Node newItem = new Node(item);
if (isEmpty())
front = newItem;
else
rear.next = newItem;
rear = newItem;
}
poll
• //Dequeue and recover old front item
Public E poll(){
E item = peek();
if (item == null) return null; //empty queue
else{
front=front.next;
if (front == null) rear = null; //removed last item
} return item;
}
Array Implementation
• Where should the front be? (easy to access
& remove, inserting not necessary)
• Where should the back be? (easy to insert,
direct access and removal not necessary)
• No real reason for either front or back to be
anywhere but beginning or end of array.
Array Implementation
• Inserting at the beginning of the array would cause a
great deal of shifting! Therefore, insert at end of
array
– Rear of queue is end of array
– Front of queue is front of array
• Remove by moving the front pointer (like top for
stack)
• Problem: once we’ve moved the front forward, we
lose access to the space behind it!
Solution
• Use a “circular array”
– Most of the time, it acts like a normal array
– The item after element MAX-1 is element 0
– Use Mod (%) operation to implement this.
• One more problem…
– How do we tell an empty array from a full array?
– When the back pointer is one slot behind the front
pointer, i.e. (back+1)%MAX = front, the array is
either empty or full!
Empty vs. Full Array
1. Keep a count of the elements in the array
•
Minimal space cost (1 integer), some time for each update
2. Keep one element free. When the queue is empty,
(back+1)%MAX = front. When the queue is full
(back+2)%MAX = front.
•
Space cost is the size of 1 queue element. This can be
(much) larger than an integer!
3. Keep a “full” flag
•
Space & time costs similar to option #1.
Array Queue Implementation
public class ArrayQueue<E>
extends AbstractQueue<E>
implements Queue<E> {
private E[] theData;
private int front, rear;
private int size, capacity; //choice 1
…
}
Constructor & isEmpty
• Constructor creates a new array
Public ArrayQueue(int initCapacity) {
theData = (E[]) new Object[capacity]);
capacity = initCapacity;
front = 0;
rear = capacity–1;
}
• isEmpty uses size (really in abstractQueue)
Public boolean isEmpty(){
return (size == 0);
}
Offer
public boolean offer(Itemtype item){
if (size == capacity) reallocate();
rear = (rear+1) % capacity;
theData[rear] = item;
size++;
return true;
}
Poll (and peek)
Public E poll(){
if (isEmpty()) return null;
E result = theData[front];
front = (front+1) %capacity;
size--;
return result;
}
//Peek is similar, but doesn’t change front or size.
Reallocate
• When the queue is full, make a new one
(double size) and copy all the data
Private void reallocate(){
//make new array
int newCapacity = 2*capacity;
E[] newData = (E[]) new Object[newCapacity];
int j=front;
//continued on next slide
Reallocate (continued)
//copy data to new array
int j=front;
for int (x=0;x<size;x++){
newData[x] = theData[j];
j=(j+1)%capacity;
}
//reset variables
front = 0; rear = size–1;
theData = newData;
} //end of reallocate
Application: Simulation
• Simulate a real-life situation
• Make several simplifying assumptions
– Time is divided into discrete time steps
– One or more events happen at each time step
• Arrival events (e.g. customer arrives at bank) -- comes
from input (time of arrival)
• Departure events (e.g. customer leaves bank) -internally determined from when transaction begins and
transaction time.
Bank example with Queue
• We want to measure how long customers wait at the
bank.
• When a customer arrives, an arrival event is
processed. Since customers should be processed in
order, they wait in a queue
• Assume that each transaction takes a fixed amount
of time. When time is up, the customer being
processed leaves, and the next customer (front of the
queue) takes their place.
Using the Simulation for Decision
Making
• Vary the number of tellers - how does the wait
time vary?
• Consider different types of “teller
transactions” with different transaction times
• Should we have an “express lane”?
• If we have multiple tellers, should each have
its own queue?
A simple pseudocode
• Read the first arrival
• For (time=0;time<max;time++){
– If it’s time for the first arrival
• Process the arrival
• Read the next arrival (assume only one arrival per time)
– If there is a departure event at this time
• Process the departure
• }
Two data structures
• Event list
– Sorted by event time
– Includes arrivals and departures
– List, not queue (why)?
• Customer queue
– Customers enter when they arrive if the teller is
busy
– Customer at front of queue “goes to the teller”
when the previous customer departs
Implementing the “teller”
• We don’t need a specific data structure for
the teller
– When a customer “goes to the teller”, a departure
event for that customer is created
– Time of departure is based on estimated
transaction time
– When the departure event is processed, that
customer “leaves the teller”, and a new customer
will be taken from the front of the queue
Searching a Graph with a Queue
• Given connections among cities, find a path from A to
B
• Revise our stack algorithm to use a queue:
q.offer (Start)
for each city visible from q.peek()
if the city is End, recover and return the path
if the city is not already on the queue,
mark city’s predecessor as q.peek()
q.offer( the city )
q.remove().