Educational Objectives: Experience applying the following concepts: associative generic containers; generic algorithms; priority queue; and developing and testing multiple implementations using namespace.
Operational Objectives: Design and implement eight (8) distinct implementations of the Priority Queue template TPriorityQueue<T,P> based on TVector<T> (3), TDeque<T> (3), and TList<T> (2). Use namespaces tpq1, tpq2, ... , tpq8 to scope the eight variations and use namespace alt to scope the generic algorithms required for some of the implementations. The work must operate correctly with the supplied test client ftpq.cpp and meet all other requirements listed below.
Deliverables: One file tpq.h
A priority queue stores elements of typename T with a priority determined by an object of a predicate class P. The operations are syntactically queue-like but have associative semantics (rather than positional semantics, as in an ordinary queue). The operations for a priority queue PQ<T,P> and informal descriptions of them are as follows:
void Push (const T& t) // places t in the priority queue, position unspecified void Pop () // removes the highest priority item from the priority queue T Front () // returns (a copy of) the highest priority item in the priority queue bool Empty () // returns true iff the priority queue is empty
as well as default constructor, destructor, copy constructor, and assignment operator (i.e., we need priority queue to be a proper type). We will also use the following additional operations (as usual for our course library):
void Clear () // makes the priority queue empty size_t Size () // returns the number of elements in the priority queue void Dump (std::ostream& os, char ofc = '\0') // send contents to os
Priority queues are used in several important applications, including:
Priority queues are traditionally built as adaptations of other data structures using special algorithms. The most sophisticated of these are discussed in the chapter Trees 1 in the context of heaps, heap algorithms, and heap sort. However, us usual, "most sophisticated" does not always translate as "best". "Best" is usually determined by client programmers based on the client needs. (Compare g_simple_sort() with g_heap_sort() for example.)
The current project will build priority queues using (1) one of our familiar p-Containers TVector<T>, TDeque<T>, TList<T> as the data storage facility; (2) possibly some associative structure on the data (an ordering); and (3) an appropriate generic algorithm for the search mechanism. In the case of heap-based priority queues (cases 7 and 8) two generic algorithms will be required.
Create and work within a separate subdirectory (called proj below). Make sure your code distribution directories are up to date by invoking your "update" command. The usual COP 4530 rules apply (see Introduction/Work Rules). In particular: It is a violation of course ethics and the student honor code to use, or attempt to use, files other than those explicitly distributed in the course code library.
Place all work in one file named tpq.h.
Turn in the file tpq.h using the pr4submit.sh script.
Warning: Submit scripts do not work on the program and linprog servers. Use shell.cs.fsu.edu to submit projects. If you do not receive the second confirmation with the contents of your project, there has been a malfunction.
Place all work in one file named tpq.h. The code should use 11 distinct namespaces: std, fsu, alt, tpq1, tpq2, tpq3, tpq4, tpq5, tpq6, tpq7, and tpq8.
The first two namespaces are those encountered in the standard and course libraries. The other nine are defined in the submitted code. These nine namespaces define the scope of certain definitions, as shown in the following sample file documentation:
/* tpq.h Various implementations for TPriorityQueue < T , P > organized by namespace as follows: nmsp stbl container element order generic algorithms push pop front ---- ---- --------- ------------- ------------------ ---- --- ----- tpq1 y vector unordered fsu::g_max_element() O(1) O(n) O(n) tpq1a n vector unordered fsu::g_max_element() O(1) O(n) O(n) tpq2 y vector sorted fsu::g_lower_bound() O(n) O(1) O(1) tpq3 y deque unordered fsu::g_max_element() O(1) O(n) O(n) tpq3a n deque unordered fsu::g_max_element() O(1) O(n) O(n) tpq4 y deque sorted fsu::g_lower_bound() O(n) O(1) O(1) tpq5 y list unordered fsu::g_max_element() O(1) O(n) O(n) tpq6 y list sorted alt::g_lower_bound() O(n) O(1) O(1) tpq7 n vector partial fsu::g_push/pop_heap() O(log n) O(log n) O(1) tpq8 n deque partial fsu::g_push/pop_heap() O(log n) O(log n) O(1) The "a" versions of tpq1 and tpq3 just copy the last element over the element to be removed, whereas the reqular versions do a leapfrog copy. Note that the latter is stable, the former is not. The namespace alt contains the generic algorithm: template <class ForwardIterator, typename ElementType, class Comparator> ForwardIterator g_lower_bound (ForwardIterator beg, ForwardIterator end, const ElementType& x, const Comparator& cmp) This version of lower bound uses the assumption of an ordered range (as in the classic version) but works for forward iterators. Thus, (1) it can be applied to TListIterators and (2) it has runtime O(n) rather than O(log n) (where n = size). All other generic algorithms needed are in the fsu namespace and are in the code libray. All of the tpq namespaces as well as the alt namespace are defined in this file. */
Every method implementation must be one of three types:
Type 1 (no search is required): the body consists of a single call to an operation of the underlying container
Type 2 (search is required): the body uses a generic search algorithm (as specified in the table above) with a minimum of ancillary code.
Type 3 (heap-based): the body uses a generic heap algorithm with a minimum of ancillary code.
Your submission is required to run correctly with the distributed client program tests/ftpq.cpp.
Start by creating a complete solution for tpq1, the simplest case. Be sure it is optimally clear and well tested. This will serve as a model for the others, and reduce the amount of organizing you have to think about. Continue creating the implementatons in order. By the time you get to the the last few, you will be able to concentrate on the algorithms.
Here is a start on tpq1, along with the include files that you will need:
/* tpq.h */ #include <genalg.h> // fsu::g_max_element() #include <gbsearch.h> // fsu::g_lower_bound() #include <gheap.h> // fsu::g_push_heap() and fsu::g_pop_heap() #include <tlist.h> // fsu::TList<> , fsu::TList<>::Iterator #include <tvector.h> // fsu::TVector<> , fsu::TVector<>::Iterator #include <tdeque.h> // fsu::TDeque<> , fsu::TDeque<>::Iterator namespace tpq1 { template <typename T, class P > class TPriorityQueue { typedef typename fsu::TVector < T > container_type; typedef typename fsu::TVector < T > :: Iterator iterator_type; typedef T value_type; typedef P predicate_type; // store elements in unsorted order in vector // Push(t): PushBack(t) // Front(): use fsu::g_max_element() to locate largest, then return element // Pop() : use fsu::g_max_element() to locate largest, then remove container_type c; predicate_type p; public: void Push (const T& t) { // TBS } void Pop () { // TBS } T& Front () { // TBS } void Clear () { // TBS } int Empty () { // TBS } size_t Size () { // TBS } void Dump (std::ostream& os, char ofc = '\0') { // TBS } }; } // namespace tpq1 namespace tpq2 { // yada dada
Clear(), Empty(), and Size() should just call the container operation of the same name. Dump() should output the contents of the underlying container. That leaves only the three operations Push(t), Pop(), and Front() requiring plan-specific implementations.
If the model above is followed for all eight versions, the default constructor, destructor, copy constructor, and assignment operator should work, so neither prototype nor implementation of these operations is necessary. (Can you explain why?)
Look carefully in ftpq.cpp to see how to declare and use TPriorityQueue<T,P> objects, in particular, how the predicate is instantiated. (We will use TLessThan<T> or TGreaterThan<T> for the predicate class in our tests.)
Look at the code in genalg.h, gbsearch.h, and gheap.h for models on how to declare alt::g_lower_bound(). You also need to come up with an implementation of alt::g_lower_bound().
alt::g_lower_bound(beg, end, t, p) uses sequential search to determine the first instance of t in the range [beg,end) (or, if t is not in the range, the location where it should be inserted), assuming that the range is sorted according to p. An iterator to the location is returned.
Sample executables ftpq?.x (? = 1,6,8) will be available in area54.