8.5 Unbounded Priority QueueThere are a number of ways to implement an unbounded Priority Queue ADT. The most basic is to use a vector or linked list as was done with the Queue ADT. To implement the priority queue, we must consider several facts related to the definition of the ADT:
Python List ImplementationWe used a Python list to implement the basic queue earlier in the chapter. In that implementation, the queue items were organized within the list from front to back with new items appended directly to the end and existing items removed from the front. That simple organization worked well with the basic queue. When implementing the priority queue, however, the items cannot simply be added directly to the list, but instead we must have a way to associate a priority with each item. This can be accomplished with a simple storage class containing two fields: one for the priority and one for the queue item. For example: class PriorityQEntry : def __init__(self, item, priority) : self.item = item self.priority = priority With the use of a storage class for maintaining the associated priorities, the next question is how should the entries be organized within the vector? Remember, the actual implementation or physical organization of the data items do not have to match the abstract view of the ADT. We can organize the data items any way we like so long as the operations are implemented to correctly provide the abstract view. Thus, we can consider two approaches, both of which satisfy the requirements of the priority queue:
In this section, we provide an implementation using the latter approach in which new items are appended to the end of the vector. The implementation of the alternative approach is left as an exercise. Most of the operations of the priority queue can be implemented in the same fashion as with the basic queue. Only the def enqueue(self, item, priority) : entry = PriorityQEntry(item, priority) self._qList.append(entry) Thus, in the physical representation the items are stored in the order in which they are enqueued, not in the order in which they will be dequeued. This can be seen in Figure 8.5.1, which illustrates an instance of the class for the priority queue from Figure 8.4.1. To dequeue an item, we can not simply remove the first item in the vector since the items are not stored in order by priority. Instead, we have to search through the vector to find the item with the highest priority. Once found, the item can be removed from the vector and returned. The def dequeue(self) : assert not self.isEmpty(), "Cannot dequeue from an empty queue." highest = self._qList[i].priority for i in range(self.len()) : if self._qList[i].priority < highest : highest = self._qList[i].priority entry = self._qList.pop(highest) return entry.item # only the item part is returned. A complete implementation of the priority queue in which new items are appended to the end of the vector is provided below. Program Listing
To evaluate the efficiency, we consider the implementation of each operation. Testing for an empty queue and determining the size can be done in time. The enqueue operation requires linear time in the worst case since the underlying array may have to be reallocated, but it has a amortized cost. The dequeue operation is also since we must search through the entire list in the worst case to find the entry with the highest priority. Linked List ImplementationA singly linked list structure with both head and tail references can also be used to implement the priority queue as illustrated in Figure 8.5.2. The As with the Python list implementation of the priority queue, testing for an empty queue and determining the size can be done in time. The enqueue operation can also be done in constant time since we need only append a new node to the end of the list. The dequeue operation, however, requires time since the entire list must be searched in the worst case to find the entry with the highest priority. Once that entry is located, the node can be removed from the list in constant time.
|