Queues. Chapter Objectives 1.Understanding and applying the Queue ADT. 2.Implementing a Queue using...
-
Upload
john-bishop -
Category
Documents
-
view
221 -
download
0
Transcript of Queues. Chapter Objectives 1.Understanding and applying the Queue ADT. 2.Implementing a Queue using...
Queues
Chapter Objectives
1. Understanding and applying the Queue ADT.
2. Implementing a Queue using an array and links.
3. Designing and programming simple simulations.
Real world examples
Cars in line at a tool booth
People waiting in line for a movie
Computer world examples
Jobs waiting ot be executed
Jobs waiting to be printed
Input buffersCharacters waiting to be processed
Common queue properties
Queues have the poperty that, the earlier an item enters a queue, the earlier it will leave the queue:
First in, first out (FIFO)
Queues and lists
A queue is a restricted form of a list.
Additions to the queue must occur a the rear.
Deletions from the queue must occur at the front
Example
Rear item is most recent addition to queue
Front item has waited Longer than all other queue entries
Items enter at rear and leave at front
Problem:
Computer network performance Author’s example: ethernet Ethernet is a set of protocols describing
how devices on a network can communicate
A sample Ethernet configuration
1 3 5 7
642
File Server Workstation Workstation Bridge
PrinterPCPC
How ethernet works
Devices place messages onto the network preceded by the id number of the device the message is intended for.
If two devices send messages at the same time a collision occurs.
Device communication
Devices operate at differing speeds PC may place document on network at 100,000
cps Ethernet transmits document at 1,250,000 cps Printer may only processes 10,000 cps
How does the printer handle the pileup of characters sent by the PC?
Print buffers
A buffer is a queue Incoming data is stored at the rear It processes data in a FIFO manner
Operation of an ethernet buffer
b c 1 2 3 x y z 0
Ethernet
Printer
next character goes here
Queue ADTCharacteristics
• A Queue Q stores items of some type (queueElementType), with First-In, First-Out (FIFO) access.
Operations
queueElementType A.dequeue()
Precondition: !isEmpty()
Postcondition: Qpost = Qpre with front removed
Returns: The least-recently enqueued item (the front).
Queue ADT operations (continued)void Q.enqueue(queueElementType x)
Precondition: None
Postcondition: Qpost = Qpre with x added to the rear.
queueElementType Q.front()
Precondition: !isEmpty()
Postcondition: None
Returns: The least-recently enqueued item
(the front).
bool Q.isEmpty()
Precondition: None
Postcondition: None
Returns: true if and only if Q is empty,
i.e., contains no data items.
Code Example
int main()
{
char c;
Queue < char > q;
Stack < char > s;
Code Example (continued)// read characters until '.' found, adding each to Q and S.
while (1) {
cin >> c;
if (c == '.') break; // when '.' entered, leave the loop
q.enqueue(c);
s.push(c);
}
while (!q.isEmpty()) {
cout << "Q: " << q.dequeue() << '\t';
cout << "S: " << s.pop() << '\n';
}
return 0;
}
Sample program output
Q: a S: c Q: b S: b Q: c S: a Queues preserve order while stacks reverse
it.
Exercise 9-1What is the output resulting from the following sequence, where q is an initially empty queue of int:
q.enqueue(1);
q.enqueue(2); // different than text
q.enqueue(3);
cout << q.front();
cout << q.dequeue();
cout << q.front();
if (q.isEmpty())
cout << “empty\n”;
else
cout << “not empty\n”;
program analysis
q.enqueue(1)
q.enqueue(3)
q.enqueue(3)
1
13 2
2 1
front
front
front
rear
rear
rear
Program output
13 2
frontrear
cout << q.front();
cout << q.dequeue();
cout << q.front();
1
13 2
front
1
rear
3 2
frontrear
2
Program continued
3 2
frontrear
if (q.isEmpty()) cout << “empty” << endl;else cout << “not empty” << endl;
not empty
Array implementation
Must keep track of front and rear (more complicated than a stack)
Rear and front
With data in a queue, implemented as an array, rear will normally be greater than front.
There are 3 special situations which must be addressed
1. Rear < front (empty queue) 2. Rear = front (one-entry queue) 3. Rear = array size (full queue)
Example: enqueueingEmpty queue
Front
Rear
Front Front
Rear Rear
Single elementqueue
Normalqueue
Various queue states
Front
Rear
Normalqueue
Front
Rear
Fullqueue
Front
Rear
dequeueing
dequeueing
Front
Rear
dequeueing
FrontRear
Moredequeueing
Front
Rear
Single element queue
A problem
Front
Rear
Single element queue
We cannot increase rear beyondthe limits of the array.Therefore, this queue must beregarded as full, even though thereare plenty of available memory cells.
One way to solve this might be toWait until the queue empties out andThen reset it and start again.
In the computer processing job examplehowever, this would be unacceptablesince many jobs could arrive while thequeue is emptying out.
Another difficultly
Front
Rear
Single element queue
Front
Rear
Is the queueempty or full?
Our definition ofempty is rear < front, so it is empty.
But, rear has reachedits limit. It cannot gobeyond the array, Therefore, the queue is full!
The solution: wrapping around
Front
Rear
Single element queue
Front
Rear
Next enqueuewraps around
The circular queue
Front
Rear
Circularqueue Rear
Front
empty
empty
empty
empty
emptyemptyemptyempty
Author’s example
Example: Queue is an array of 4 elements We wish to enqueue and dequeue multiple
items
enqueue and dequeue operations
a b
a ? ? ?
? ?
a b c ?
a b c ?
a b c d
a b c d
f r
enqueue('a')
enqueue('b')
enqueue('c')
dequeue()
enqueue('d')
dequeue()
enqueue('e') ? ? ? ?
f r
f r
f r
f r
rf
A paradox
With an array, it is easy to run out of space in the queue to enqueue items and yet still have unused memory cells that can store data!
To get around this, we have to make it possible to ‘wrap around’
Both ‘rear’ and ‘front’ must be able to wrap around.
How to wrap around
If rear + 1 > maxQueue -1 then set rear to 0 OR rear = (rear+1) % maxQueue
A Circular Queue
f
r
a
bc
d
Main issues (full and empty)
Wrapping around allows us to avoid an erroneous ‘full’
But, it means that we cannot use ‘rear < front’ as a test of whether the queue is empty because rear will become < front as soon as it wraps around
Rear and front If there is one element in the queue then
rear should be the same as front, therefore, rear must start < front.
You could check for an empty queue with something like: nextPos(rear) == front
But, when the queue is full it is also true that nextPos(rear) == front!
Queue implementation in which full and empty queues cannot be distinguished
fr fr
a
f
r
a
bc
f
a
bc
r
d
Solution
To solve this dilemma we let the definition of empty be rear==front
Where front points to an empty cell (like the header node on linked lists).
Then the test for empty is rear==front And the test for full is nextPos(rear) ==
front
Corrected queue implementation demonstrated
r r
a
r
a
b
a
bc
ff
f f
r
Alternate queue implementations(poor vs. good design)
r r
a
r
a
b
a
bc
ff
f f
r
fr fr
a
f
r
a
bc
f
a
bc
r
d
Explanation using conventional arrays
Front
Rear
Empty queue ((rear + 1) == front)
Front
Rear
Wrapped around
Front
Rear
Full queue((rear+1) == front)
Solution
Front
Rear
Empty queue (rear == front)
Front
Rear
Wrapped around
Front
Rear
Full queue((rear+1) == front)
Queue template
const int maxQueue = 200;
template < class queueElementType >
class Queue {
public:
Queue();
void enqueue(queueElementType e);
queueElementType dequeue();
queueElementType front();
bool isEmpty();
private:
int f; // marks the front of the queue
int r; // marks the rear of the queue
queueElementType elements[maxQueue];
};
NextPos(), does the wrap around
int nextPos(int p)
{
if (p == maxQueue - 1) // at end of circle
return 0;
else
return p+1;
}
Constructor
template < class queueElementType >
Queue < queueElementType >::Queue()
{ // start both front and rear at 0
f = 0;
r = 0;
}
enqueue()
template < class queueElementType >
void
Queue < queueElementType > :: enqueue(queueElementType e)
{ // add e to the rear of the queue, // advancing r to next position
assert(nextPos(r) != f);
r = nextPos(r);
elements[r] = e;
}
Dequeue()
template < class queueElementType >
queueElementType
Queue < queueElementType >::dequeue()
{
// advance front of queue, // return value of element at the front
assert(f != r);
f = nextPos(f);
return elements[f];
}
front()
template < class queueElementType >
queueElementType
Queue < queueElementType >::front()
{
// return value of element at the front
assert(f != r);
return elements[nextPos(f)];
}
isEmpty()
template < class queueElementType >
bool
Queue < queueElementType >::isEmpty()
{
// return true if the queue is empty, that is,
// if front is the same as rear
return bool(f == r);
}
Dynamic queues The advantages of linked list
implementation are The size is limited only by the pool of available
nodes (the heap) There is no need to wrap around anything.
Advantage of using the heap No need for program to check for a full queue
(this is handled by the ‘new’ function.
Header for queue as dynamic list
template < class queueElementType >
class Queue {
public:
Queue();
void enqueue(queueElementType e);
queueElementType dequeue();
queueElementType front();
bool isEmpty();
Private section
private:
struct Node;
typedef Node * nodePtr;
struct Node {
queueElementType data;
nodePtr next;
};
nodePtr f;
nodePtr r;
};
Implementation file, constructor
template < class queueElementType >
Queue < queueElementType >::Queue()
{
// set both front and rear to null pointers
f = 0;
r = 0;
}
enqueue()template < class queueElementType >
void Queue < queueElementType > ::enqueue(queueElementType e)
{// create a new node, insert it at the rear of the queue
nodePtr n(new Node);
assert(n);
n->next = 0;
n->data = e;
if (f != 0) { // existing queue is not empty
r->next = n; // add new element to end of list
r = n;
} else {// adding first item in the queue
f = n; // so front, rear must be same node
r = n;
}
}
A single-element queue
Front
Rear
data
A single element queueFront == rear
enqueueing
Rear n
Rear n
Rear n
dequeue()
template < class queueElementType >
queueElementType
Queue < queueElementType >::dequeue()
{ assert(f); // make sure queue is not empty
nodePtr n(f);
queueElementType frontElement(f->data);
f = f->next;
delete n;
if (f == 0) // we're deleting last node
r = 0;
return frontElement;
}
dequeueing
Rear
Front
nRearFront
RearFront
front()
template < class queueElementType >
queueElementType
Queue < queueElementType >::front()
{
assert(f);
return f->data;
}
isEmpty()
template < class queueElementType >
bool
Queue < queueElementType >::isEmpty()
{
// true if the queue is empty -- when f is a null pointer
return bool(f == 0);
}
Simulation
Modeling a computer network Each device is connected to the network by
a ‘controller’ Before a controller sends a message it
checks to see if the network is busy. If so, it waits. If not, it sends the message.
Collisions
When two controllers attempt to send a message at the same time the messages collide and the receiving controllers ignore it.
Similarly, the sending controllers recognize the problem and resend their messages.
Queues
Queues are needed to collect data. The sending controllers need them because
they must have the capability of storing data until the network is not busy.
Receiving controllers need queues because they cannot process data as fast as the network can send it.
Device-controller interface
device queue controller
Ethernet
Network collision rates One measure of a network is its ‘effective
bandwidth ratio’ This is the ratio of the amount of data
actually transmitted and the maximum possible data the network can transmit.
A network that sends data one quarter of the time has a bandwidth ratio of .25
Bandwidth and collisions
As the number of messages sent on the network goes up, so does the bandwidth ratio - to a point
When too many collisions occur the bandwidth ratio may go down because messages are being garbled and hence ignored.
Ethernet simulation
int main()
{ randomize(); // provide seed to random generator
int steps;
cerr << "Enter number of steps: ";
cin >> steps;
int maxCnts;
cerr << "Enter max number of controllers: ";
cin >> maxCnts;
int cnts;
for (cnts = 10; cnts <= maxCnts; cnts+=10) // increase by 10 each pass
{
Controller * ctrl[1000];
int i;
for (i=0; i < cnts; i++) // more controllers created each pass
ctrl[i] = new Controller;
Code Example 9-6
long int collisions(0), transmissions(0);
int p;
for (p = 0; p < steps; p++) {
int senders(0);
int netChar(netQuiet);
int c;
for (c = 0; c < cnts; c++) {
int ch(ctrl[c]->phase1()); // get message, if any, from device
if (ch != 0) {
senders++;
netChar = ch;
}
}
Code Example 9-6 if (senders > 1) {
netChar = netCollide;
collisions++; // count collisions
}
else if (senders == 1)
transmissions++; // count successful transmissions
for (c = 0; c < cnts; c++)
ctrl[c]->phase2(netChar);
}
cout << "---\nControllers: " << cnts << "\n";
cout << "Collision count: " << collisions << "\n";
cout << "Transmission count: " << transmissions << endl;
for (i = 0; i < cnts; i++)
delete ctrl[i];
} return 0;
}
Controller class
enum { netQuiet = 0, netCollide = -100 };
const long maxWait = 20;
class Controller {
public:
Controller();
int phase1(); // update the device and check its state
void phase2(int netchar); // receive network contents
private:
enum state { Idle, Listen, Wait, Send };
int currentState;
Queue < char > outQ;
Device dev;
int waitTime;
};
Controller State Machine
Each step begins with one of these four states: idle, listen, wait or send
Depending on the result of the phase1 call and the contents of the controller queue, the device may stay in the same state or move to a new one.
State diagram for network controller
netColli
de
inchar
endMessage
waitTime==0
!netQuiet
IDLE
LISTEN SEND
WAIT
netQuietendMessage
Controller constructor
Controller::Controller()
{
currentState = Idle;
}
Phase I : update and check device
int Controller::phase1()
{
int inchar(dev.update()); // update device
int outchar(0);
if (inchar)
outQ.enqueue(inchar); // put character in controller queue
switch(currentState) {
case Idle:
if (inchar)
currentState = Listen;
break;
Phase I, continued
case Listen:
case Wait:
break;
case Send:
if (outQ.isEmpty())
currentState = Idle;
else
outchar = outQ.front();
break;
}
return outchar;
}
Phase II: listen to networkvoid Controller::phase2(int netchar)
{
int outchar;
switch(currentState) {
case Idle:
break;
case Listen:
if (netchar == netQuiet)
currentState = Send;
else {
waitTime = random(maxWait) + 1;
currentState = Wait;
}
break;
Phase II, continued
case Wait:
if (waitTime > 0)
waitTime--;
else
currentState = Listen;
break;
case Send:
if (netchar == netCollide) {
waitTime = random(maxWait) + 1;
currentState = Wait;
}
Phase II, continued else {
outchar = outQ.dequeue();
if (outchar == endMessage)
if (outQ.isEmpty())
currentState = Idle;
else {
waitTime = random(maxWait) + 1;
currentState = Wait;
}
}
break;
}
}
The Device Classconst int avDelay = 5000;
const int maxMsg = 10;
const int endMessage = -1;
class Device {
public:
Device();
int update();
private:
enum state { Idle, Sending };
state currentState;
int messageLength;
};
Device constructor
Device::Device()
{
currentState = Idle;
}
update() device sending int Device::update() {
int outChar(0);
// if we're sending, put a character out to the controller
if (currentState == Sending) {
if (messageLength--)
outChar = messageLength + 32; // convert number to printable digit
else {
currentState = Idle;
outChar = -1;}
}
Update() device not sending
// if not, pick random number to decide whether to start sending
else // currentState == Idle
if (random(avDelay) == 0) {
messageLength = random(maxMsg) + 1;
currentState = Sending;
}
return outChar;
}
Sample data from network simulation
0
10000
20000
30000
40000
50000
60000
transmissions
collisions
100 200 300 400 500
Number of Controllers
Priority queues
Queues are often prioritized Example, UMD registration queue
Priority 1: Seniors Priority 2: Juniors Priority 3: Sophomores Priority 4: Freshmen
Other priority queues
Operating systems often prioritize their print queues so that large output does not monopolize the printer during peak times.
Example Priority 1: 5 pages or less Priority 2: 6-30 pages Priority 3: >30 pages
Pointers with priority queues
If you have three priorities (see last example) you would need three rear pointers
Priority print queueFront
Rear 1 (5 pages or less)
Rear 2 (6-30 pages)
Rear 3 (> 30 pages)
statssim
copybank
checkupdateaverag
test
Processing a new job
Front
Rear 1 (5 pages or less)
Rear 2 (6-30 pages)
Rear 3 (> 30 pages)
statssim
copybank
checkupdateaverag
test
If a new job called ‘prob1’was sent to the printer andrequired 25 pages to beprinted, it would beplaced at the rear of thesecond priority (Rear 2).
prob1
Enqueueing of a priority 2 job
Front
Rear 1 (5 pages or less)
Rear 2 (6-30 pages)
Rear 3 (> 30 pages)
statssim
copybank
check
updateaverag
test
prob1
Note that if a priorityqueue were implementedusing an array, theaddition of an item mayrequire significantdata movement.
Linked lists are the betterchoice in this instance.
What does this indicate?
Front
Rear 1 (5 pages or less)
Rear 2 (6-30 pages)
Rear 3 (> 30 pages)
foobar
boomzowie
frob
snarlmuddleheaded
zilch
Priority 2 is empty
Front
Rear 1 (5 pages or less)
Rear 2 (6-30 pages)
Rear 3 (> 30 pages)
foobar
boomzowie
frob
snarlmuddleheaded
zilchIn general, If rear(n-1) == rear(n)Then priority n is empty.