Queues

advertisement
Queues
A queue is an ordered collection of data items such that:
items can be removed only at one end ( front of the queue)
items can be added only at the other end ( back of the queue)
Basic operations are:
construct:
empty:
addQ:
front:
removeQ:
Create an empty queue
Check if a queue is empty
Add a value at the back of the queue
Retrieve the value at the front of the queue
Remove the value at the front of the queue
Queues
a queue is a First-In-First-Out (FIFO) structure.
Examples:
I/O buffers
Scheduling queues in a multi-user computer system
e.g. Printer queue:
When files are submitted to a printer, they are placed in the printer queue.
The printer software executes an algorithm something like:
for (;;)
{
while (printerQueue.empty())
sleep 1;
printFile = printerQueue.removeQ();
Print(printFile);
}
Queues
Resident queue: On disk, waiting for memory
Ready queue:
In memory —needs only CPU to run
Suspended queue:Waiting for I/O transfer or to be reassigned the CPU
Queues vs. Stacks
Stacks are a LIFO container  store data in the reverse of order received
Queues are a FIFO container => store data in the order received
Stacks then suggest applications where some sort of reversal or unwinding is
desired.
Queues suggest applications where service is to be rendered relative to order
received.
Stacks and Queues can be used in conjunction to compare different
orderings of the same data set.
From an ordering perspective, then, Queue are the “opposite” of stacks
 Easy solution to the palindrome problem
Easy Palindrome Check
#include “Stack.h”;
#include “Queue.h”;
#include <iostream.h>
using namespace std;
int main()
{
Stack S;
Queue Q;
char
ch;
cout << “Enter string to be tested as palindrome: “;
while (cin >> ch)
{
S.push(ch);
Q.addq(ch);
}
bool pal = true;
while (!Q.empty())
pal = Q.removeq() = = S.pop();
if (pal)
cout << “Palindrome!!” << endl;
return 0;
}
Array Implementation
Any implementation of a queue requires:
storage for the data as well as
markers (“pointers”) for the front and for the back of the queue.
An array-based implementation would need structures like
myArray, an array to store the elements of the queue
myFront, an index to track the front queue element
myBack, an index to track the position following last queue element
Additions to the queue would result in incrementing myBack.
Deletions from the queue would result in incrementing myFront.
Clearly, we’d run out of space soon!
Solutions include:
Shifting the elements downward with each deletion (YUCK!!)
Viewing array as a circular buffer, i.e. wrapping the end to the front
“Circular” Array-Implementation
Wraparound keeps the addition/deletion cycle from walking off the edge
of the storage array.
Say, myArray has QUEUE_CAPACITY elements.
When myBack hits the end of myArray, a deletion should wrap
myBack around to the first element of myArray, viz:
myBack++;
if (myBack = = QUEUE_CAPACITY)
myBack = 0;
//equivalently (preferred, concise)
myBack = (myBack + 1) % QUEUE_CAPACITY;
Analogous handling of myFront needed.
“Circular” Array-Implementation
Initially, a queue object is empty.
 myFront = = 0
 myBack = = 0
After many insertions and deletions, the queue is full
 First element, say, at myArray[i]
 myFront has value of i.
 Last element then at myArray[i-1] (i > 0)
 myBack has value of i.
PROBLEM: How to distinguish between empty & full??
Common Solutions:
Keep an empty slot between myFront and myBack,
i.e. myArray allocated QUEUE_CAPACITY + 1 elements
Keep an auxiliary counter to track actual number of elements in queue
QUEUE class
#ifndef QUEUE
#define QUEUE
const int QUEUE_CAPACITY = 128;
typedef int QueueElement;
class Queue
{
/***** Function Members *****/
public:
Queue();
bool empty() const;
bool full() const;
void addQ(const QueueElement & value);
QueueElement front const();
//nondestructive “peek”
void removeQ();
/***** Data Members *****/
private:
QueueElement myArray[QUEUE_CAPACITY];
int myFront,
myBack;
}; // end of class declaration
#endif
QUEUE class implementation
Queue::Queue()
{
myFront = myBack = 0;
}
bool Queue::empty()
{
return myFront == myBack;
}
bool Queue::full()
{
return myFront == (myBack + 1) % QUEUE_CAPACITY;
}
QUEUE class implementation
void Queue::addQ(const QueueElement& value)
{
if (myFront != (myBack + 1) % QUEUE_CAPACITY)
{
myArray[myBack] = value;
myBack = (myBack + 1) % QUEUE_CAPACITY;
}
return;
}
void Queue::removeQ()
{
myFront = (myFront + 1) % QUEUE_CAPACITY;
}
Linked list implementation
Linked list implementation is another approach for queue representation.
The interface to the class Queue
empty, addq, removeq, front, etc. would not change
The storage structure would be a linked list.
The markers would be pointers now rather than indices into an array.
myFront would contain the address of the first node in this list
myBack would contain the address of the last node in this list
empty would test if myFront was a nonnull pointer,
addq would allocate a new node, link it off myBack and update myBack,
removeq would remove the first element and update myFront,
front would return a copy of the element whose address was in myFront
Deque
A deque (double-ended queue) is similar to a queue
BUT additions and deletions may be performed on either end.
Hence, a implementation needs a directive (tag) indicating at what end the
operation is to be performed OR multiple methods should be provided.
We modify a queue's circular array implementation here.
#include <iostream>
using namespace std;
enum where {front, rear};
/* Deque implemented with same data members
myArray
myFront
myBack
Constructors and empty(), full() methods the same
*/
Deque enqueuing
void Deque::addQ(int item, where w)
{
if ((myBack +1)% QUEUE_CAPACITY == myFront)
cout << "FULL, cannot add to queue. Error!! "
<< endl;
else if (w == rear)
// regular enqueuing
{
myArray[myBack] = item;
myBack = (myBack+ 1) % QUEUE_CAPACITY;
}
else
// enqueue at front
{
if (!myFront)
myFront = QUEUE_CAPACITY;
else
myFront++;
myArray[myFront] = item;
}
return;
}
Deque enqueuing
//better to provide two addition methods
void Deque::push_front(int item)
{if ((myBack +1)% QUEUE_CAPACITY == myFront)
cout << "FULL, cannot add to queue." << endl;
else
// enqueue at front
{ if (!myFront)
myFront = QUEUE_CAPACITY;
else
myFront--;
myArray[myFront] = item;
}
return;
}
void Deque::push_back(int item)
{
if ((myBack +1)% QUEUE_CAPACITY == myFront)
cout << "FULL, cannot add to queue." << endl;
else
// regular enqueuing
{
myArray[myBack] = item;
myBack = (myBack+ 1) % QUEUE_CAPACITY;
}
return;
}
Deque dequeuing
int Deque::pop_front()
// assume non-empty
{
int item = myArray[myFront];
myFront= (myFront+ 1) % QUEUE_CAPACITY;
return item;
}
void Deque::pop_back()
// assume non-empty
{
if (!myBack)
myBack = QUEUE_CAPACITY;
else
myBack--;
int item = myArray[myBack]; // dequeue from rear
return item;
}
deques
The deque is a standard container provided by STL.
It is easily implemented by the vector type.
See chapter 6 for more details.
Priority Queues
Priority queues are queues in which items are ordered by priority rather
than temporally (i.e. order in which received).
Any queue implementation then can be taken and modified to acquire a
priority queue.
The only needed modification is the addq() method.
Instead of unconditionally adding to the back, the queue must be scanned
for the correct insertion point.
An array implementation then would require shifting elements (YUCK).
Hence a linked list implementation is preferred.
(Develop then the preferred solution after chapter 8)
Priority Queues
class PriorityQ
{
public:
PriorityQ();
void addq(int);
int removeq();
bool empty();
bool full();
int back();
int size();
private:
const int
PRIORITYQ_CAPACITY = some_value;
int
myArray[PRIORITYQ_CAPACITY];
int
myFront;
int
myBack;
};
Download