Industrial Training




Priority Queue



Stacks and queues are simple, practical abstract data types that can be implemented very efficiently - their basic operations can be implemented to run in worst-case constant time. Here we will consider a new data type, very similar to a queue, also simple and also very practical, but in which efficient implementation is not so easy. A priority queue is a data structure for maintaining a collection of items each with an associated key (or priority). A priority queue supports the following operations:

insert - add an item and its key to the priority queue
maximum (or minimum) - return the item of highest priority
extractMax (or extractMin) - remove the item of highest priority and return it
as well as the now standard isEmpty, isFull, and size operations.
If each item's priority is taken to be time at which it is inserted, then we get a first-in-first-out structure (i.e. a queue), thus the name priority queue.

What happens if two different items have the same priority? There are several options. As stated so far, there is no rule - so which ever item is extracted first is arbitrary. Alternatively, we can, as Main & Savitch do, treat the item that arrives first as if it had higher priority. In any case, this should be documented in specification of the priority queue. (Note: in class, our first implementation (with arrays), makes no guarantees about the order of which items will be extracted if they have the same priority. Our second implementation (the bounded priority queue) takes the approach that if two items have the same priority the first one on the queue will be the first one off. Using this approach we can see that if all items on the queue have the same priority, the priority queue behaves just as a queue - perhaps, another reason for the name of this ADT.)

Interface
The interface for a priority queue class template might look like:

template 
class PQueue {
public:
  ...
  Item maximum() const;
  void insert(const Item& it, const Key& key);
  Item extractMax();
  ...
private:
  ...
};



Notice that we make the class template over two class parameters, so that we can store items of most any types (that have copy constructors and assignment operators) and have priorities (keys) of a range of types that support comparison. Priority Queue Applications

Operating systems. One place where a priority queue is often needed is in the process manager of an operating system. Lots of tasks/users are vying for limited resources. How should the OS choose which task should be executed next? FIFO is a possibility - but not the most practical. One long task could prevent many short (but potentially important) ones from being accomplished. By assigning priorities to the tasks and using a priority queue, time can be used more wisely and disgruntled users can be kept at a minimum.

Time-activated events. We may have a collection of events that we are scheduled to begin at varying times. We can use a priority queue to prepare for the event slated to begin next.

Managing responsibilities. Closer to home, as a student, you may have a plethora of responsibilities, with varying degrees of importance. For
example:
watching the Packer game (most important)
doing your homework (important)
cleaning your room (not that important)
studying for 367 exam (least important)
You could use a priority queue to inform you as to which task you should concern yourself with next.

Sorting. We can sort with a priority queue. See the related assignment.
Implementations
There are many different ways to implement priority queues. The choice of which implementation to use is largely based on the resulting run-time complexity of the three basic priority queue operations (insert, maximum, extractMax). Ordered lists. Observe that if we keep a list of items in sorted order (by priority), then we can extract the item of highest priority in constant time very easily - it is one of the ends of the list. However, sorting can be expensive. Also inserting in such a way as to maintain the order can be O(n). We'll return to ordered lists later and how they might be used for priority queues later in the semester.

Arrays. As with stacks and queues we can implement priority queues in a straightforward way using arrays. This is discussed further below.

Bounded priority queues. As outlined in Main & Savitch, if we have a fixed number of different priorities, we can use an array of queues to implement priority queues efficiently - assuming that the number of possible priorities is significantly smaller than the number of items stored on the priority queue. This implementation is discussed further below.

Heaps. Later in the semester, we will see how to implement priority queues using a data structure called a heap that enables us to perform maximum in O(1) and insert and extractMax in O(lg(n)). Array Implementation

A straightforward array implementation of priority queues can be found on-line here. As pointed out by several students in class, this implementation does not preserve the order of arrival of elements of equal priority.

The private part of the class looks like:



template 
class PQueue {
public:
  static const size_t CAPACITY = ...
  ...
private:
  struct IKPair {
    Item val;
    Key k;
  };
  IKPair data[CAPACITY];  // an array of item/key pairs
  size_t count;           // how many items on the PQ
  size_t max;             // index of highest priority
};

struct IKPair is an example of a nested class - a class defined within another class. Since it has been declared in the private section, only PQueue (or its friends, if it has any) can use IKPair. The keyword struct indicates a class whose member are public by default. The invariant for this implementation is that data[max] always contains the Item/Key pair of highest priority.

It's easy to see that insert and maximum run in constant time - they each only do a handful of simple operations without loops. However, extractMax takes linear time (O(n)) since we must walk through the remaining item/key pairs in the array to find the pair with the next highest priority. This can be too slow for application that need to make frequent use of the extractMax function.

A slightly more complicated array implementation of priority queues can be found on-line here. This implementation uses circular arrays in such a way that the order of arrival of elements of equal priority is preserved.

Bounded Priority Queues
We can use an array of queues to implement a priority queue. One queue is used for each priority level. This restricts our keys to be of a integral type (so they may be indices into the array). Furthermore, there is a maximum number of different priority which must be fixed in advance. An implementation of a bounded priority queue template class can be found here.
In the implementation we reuse a Queue class template. Recall, that our queue interface provided no way to examine the front of the queue other than by calling dequeue which removes the item at the front. Thus, it would be messy to implement the maximum function for priority queues with this implementation. However, not all priority queues need a separate maximum function (separate from extractMax) and for simplicity, we define our bounded priority queue without it. The interface is:

template 
class BPQueue {
public:
  ...
  insert
  extractMax
  ...
private:
  Queue qs[priCap]; // array of queues
  size_t count;    // total number of item in PQ
  size_t highest;  // index of highest priority level 
                   // with a non-empty queue
                   


As with our array implementation of priority queues, insert runs in constant time. How fast is extractMax for bounded priority queues? In the worst case, after removing an item, our priority queue will be empty and we will need to iterate through all the queues in the array to find the next highest priority item. This is O(k) where k is the size of the the array. Notice that k is essentially fixed (i.e. a constant) in terms of the number of possible items placed in the queue. So for small k compared to n (number of items in the queue) we can say that extractMax is O(1). So bounded priority queues provided are efficient. However, they do limit the number and kind of priorities. So it may be not be ideal for certain applications (for instance if each item may have a specific time, other than its arrival time, associated with it).




Hi I am Pluto.