Note: This assignment is used to assess some of the required ABET outcomes for the degree program. The outcomes assessed here are: (a) an ability to apply knowledge of computing and mathematics appropriate to the discipline (graph theory, big-O notation) (c) an ability to design, implement, and evaluate a computer-based system, process, component, or program to meet desired needs (i) an ability to use current techniques, skills, and tools necessary for computing practice These will be assessed using the following specific outcomes and scoring rubric
In order to earn a course grade of C- or better, the assessment must result in Effective or Highly Effective for five (5) of the specific outcomes in the rubric. |
Educational Objectives: After completing this assignment, the student should be able to accomplish the following:
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.
Deliverables: Two files tpq.h and log.txt
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') // display underlying structure
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_insertion_sort() with g_heap_sort() for example.)
In this assignment you 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.
The official development/testing/assessment environment is: gnu g++ on the linprog machines.
Create and work within a separate subdirectory. 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 files tpq.h and log.txt using the hw3submit.sh script.
Warning: Submit scripts do not work on the program and linprog servers. Use shell.cs.fsu.edu to submit assignments. 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() AO(1) O(n) O(n) tpq1a n vector unordered fsu::g_max_element() AO(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() AO(1) O(n) O(n) tpq3a n deque unordered fsu::g_max_element() AO(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 leapfrog copy versions are stable, the 1-element copy versions are 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 binary search version) but works for forward iterators. Thus, (1) it can be applied to TListIterators and (2) it has runtime O(n) rather than Θ(log n) (where n = size). All other generic algorithms needed are in the fsu namespace and are in the code library. 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. We will assess using ftpq.cpp and another client program that uses a priority queue to sort data.
The log file should contain (1) statements of the runtime of the various priority queue methods, (2) explanations [informal proofs] that your statements are correct, and (3) testing procedures and results of testing. (See specific ABET outcomes iii and iv above.)
The file level documentation should contain brief descriptions of the design for each implementation of priority queue. (See specific ABET outcome v above). Please also include this in your log file.
There are four things you have to invent: tpq1::TPriorityQueue, tpq2::TPriorityQueue, alt::g_lower_bound, and tpq7::TPriorityQueue. The remaining implementations are essentially copies of these.
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 > ContainerType; typedef T ValueType; typedef P PredicateType; // 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 PredicateType p_; ContainerType c_; public: TPriorityQueue() : p_(), c_() {} explicit TPriorityQueue(P p) : p_(p), c_() {} void Push (const T& t) { // TBS } void Pop () { // TBS } const T& Front () const { typedef typename fsu::TVector < T > :: Iterator IteratorType; IteratorType i = fsu::g_max_element (c_.Begin(), c_.End(), p_); return *i; } void Clear () { // TBS } bool Empty () const { // TBS } size_t Size () const { // TBS } void Dump (std::ostream& os, char ofc = '\0') const { // TBS } }; } // namespace tpq1 namespace tpq2 { // yada dada
Note that we have used in-class implementations, as we did with the adaptor classes CStack and CQueue. This is an acceptable practice for classes like TPriorotyQueue that are essentially adaptors of existing technology to a new interface.
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.
Note that an implementation of Front() is supplied above, as an illustration of how to apply generic algorithms.
Note that there are two constructors. The first is the parameterless, or "default", constructor. It creates the predicate object using the default PredicateType constructor. The second takes a predicate object as an argument, so that the client can supply whatever exotic priority scheme is desired. By tradition established in the STL, function object arguments are passes by value.
If the model above is followed for all eight versions, the default destructor, copy constructor, and assignment operator should work, so neither prototype nor implementation of these operations is necessary. (Can you explain why?)
The following operations can use the identical implementation across all the namespaces: Constructors and other proper type operations, Clear(), Empty(), and Size(). Thus the only operations whose implementation varies from one namespace to another are the three that define the priority queue concept: Push(), Pop(), and Front().
The term "IteratorType" is defined locally as a convenience only. This type is an iterator for the underlying structure, not an iterator for PriorityQueue.
Note the "const correctness" indicated in the example. Methods that should not disturb the priority queue are specified const. And the return type of Front() is a const reference, which prevents clients from modifying the element in the priority queue (which could disrupt the internal structure of the implementation).
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.
Here are the "implementation plan" documentation statements for all 8 versions:
namespace tpq1 { ... typedef typename fsu::TVector < T > ContainerType; typedef T ValueType; typedef P PredicateType; // 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" // "remove" can be done two ways: // (1) copy elements down one index, starting at popped element, then PopBack() // Note that (1) is stable and O(n) // (2) copy last element to popped element, then Popback() // Note that (2) is unstable but O(1) // (2) suggested by Janice Murillo March 2004 // In either case, both Front() and Pop() are O(n) // due to the call to g_max_element(). ... } // namespace tpq1 namespace tpq1a { ... typedef typename fsu::TVector < T > ContainerType; typedef T ValueType; typedef P PredicateType; // 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" // "remove" can be done two ways: // (1) copy elements down one index, starting at popped element, then PopBack() // Note that (1) is stable and O(n) // (2) copy last element to popped element, then Popback() // Note that (2) is unstable but O(1) // (2) suggested by Janice Murillo March 2004 // In either case, both Front() and Pop() are O(n) // due to the call to g_max_element(). ... } // namespace tpq1a namespace tpq2 { ... typedef typename fsu::TVector < T > ContainerType; typedef T ValueType; typedef P PredicateType; // store elements in increasing order in vector // last element is largest // Push(t): use fsu::g_lower_bound () to locate insertion point for t // Front(): return back element of vector // Pop() : pop back of vector ... } // namespace tpq2 namespace tpq3 { ... typedef typename fsu::TDeque < T > ContainerType; typedef T ValueType; typedef P PredicateType; // store elements in unsorted order in deque // 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" // "remove" can be done two ways: // (1) copy elements down one index, starting at popped element, then PopBack() // Note that (1) is stable and O(n) // (2) copy last element to popped element, then Popback() // Note that (2) is unstable but O(1) // (2) suggested by Janice Murillo March 2004 // In either case, both Front() and Pop() are O(n) // due to the call to g_max_element(). ... } // namespace tpq3 namespace tpq3a { ... typedef typename fsu::TDeque < T > ContainerType; typedef T ValueType; typedef P PredicateType; // store elements in unsorted order in deque // 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" // "remove" can be done two ways: // (1) copy elements down one index, starting at popped element, then PopBack() // Note that (1) is stable and O(n) // (2) copy last element to popped element, then Popback() // Note that (2) is unstable but O(1) // (2) suggested by Janice Murillo March 2004 // In either case, both Front() and Pop() are O(n) // due to the call to g_max_element(). ... } // namespace tpq3a namespace tpq4 { ... typedef typename fsu::TDeque < T > ContainerType; typedef T ValueType; typedef P PredicateType; // store elements in increasing order in deque // last element is largest // Push(t): use fsu::g_lower_bound () to locate insertion point for t // Front(): return back element of deque // Pop() : pop back of deque ... } // namespace tpq4 namespace tpq5 { ... typedef typename fsu::TList < T > ContainerType; typedef T ValueType; typedef P PredicateType; // store elements in unsorted order in list // Push(t): PushBack(t) (or PushFront(t)) will do // Front(): use fsu::g_max_element() to locate largest, then return element // Pop() : use fsu::g_max_element() to locate largest, then remove ... } // namespace tpq5 namespace tpq6 { ... typedef typename fsu::TList < T > ContainerType; typedef T ValueType; typedef P PredicateType; // store elements in increasing order in list // last element is largest // Push(t): use alt::g_lower_bound () to locate insertion point for t // Front(): return back element of list // Pop() : pop back of list ... } // namespace tpq6 namespace tpq7 { ... typedef typename fsu::TVector < T > ContainerType; typedef T ValueType; typedef P PredicateType; // store elements in partial order using heap algorithms // first element is largest // Push(t): c.PushBack(t) followed by g_push_heap() // Front(): c.Front() // Pop() : g_pop_heap() followed by c.PopBack() ... } // namespace tpq7 namespace tpq8 ... typedef typename fsu::TDeque < T > ContainerType; typedef T ValueType; typedef P PredicateType; // store elements in partial order using heap algorithms // first element is largest // Push(t): c.PushBack(t) followed by g_push_heap() // Front(): c.Front() // Pop() : g_pop_heap() followed by c.PopBack() ... } // namespace tpq8
Sample executables ftpq?.x (? = 1,6,8) will be available in area51.