Queues. Chapter Objectives 1.Understanding and applying the Queue ADT. 2.Implementing a Queue using...

Preview:

Citation preview

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.

Recommended