Queues
Queues
Defn. As a data structure, a queue is an ordered collection of data items with the property that items can be removed only at one end, called the front of the queue, and items can be added only at the other end, called the back of the queue. Basic operations are: Queue: Front: Remove ONLY Back: Add ONLY construct: Create an empty queue empty: Check if a queue is empty addQ: Add a value at the back of the queue front: Retrieve the value at the front of the queue removeQ: Remove the value at the front of the queue Whereas a stack is a Last-In-First-Out (LIFO) structure, a queue is a First-In-First-Out (FIFO) or First-Come-First-Served (FCFS) structure.
Examples:
a. I/O buffers: queues, scrolls, deques From a file: (queue)
Disk Memory
Input Buffer
CPU
X Infile >> X;
Keyboard
Memory
Backspace
Other Queues:
1. Resident queue: 2. Ready queue:
3. Suspended queue:
On disk, waiting for memory In memory has everything it needs to run, except the CPU Waiting for I/O transfer or to be reassigned the CPU
1 2
c. CPU Scheduling:
Probably uses a priority queue: Items with lower priority are behind all those with higher priority. (Usually a new item is inserted behind those with the same priority.)
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, wed 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.
Circular Array-Implementation II
myBack: myFront:
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??? Some Common Solutions: 1. Keep an empty slot between myFront and myBack, i.e., myArray uses QUEUE_CAPACITY - 1 elements 2. Keep an integer counter to track actual number of elements in queue 3. Keep a boolean full variable to keep track of if queue is full or not
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
Deque
A deque (double-ended queue) is similar to a queue BUT additions and deletions may be performed on either end. Hence, an 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.
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; };