We have encountered and put to use the concept of generic classes, particularly generic containers. Generic containers could be characterized as implementations of ADTs that are re-usable across a broad spectrum of client programs. There is another equally important component to generic programming, generic algorithms. A generic algorithm is an implementation of an algorithm that is re-usable across a broad spectrum of containers.
The key idea facilitating generic algorithms in C++ is the iterator. By writing
algorithm implementations interacting with iterators rather than directly with
containers, the code can be applied directly to any container whose iterators
satisfy the needs of the algorithm. Iterators form the interface between a
generic algorithm and a container.
The interfacing of an algorithm with a container is accomplished by using iterators as template parameters for the algorithm. This slide shows a typical example, the generic copy algorithm. The template parameters are "I" and "J". The algorithm uses local variables of these types:
I source_beg, source_end; // defines a range of values in a source container J dest_beg; // defines the beginning of a range in a destination container
and procedes to copy elements from the source range to the target range using a loop:
while (source_beg != source_end) *dest_beg++ = *source_beg++;
This dense but efficient code is equivalent to the following:
i = source_beg; j = dest_beg; while (i != source_end) { *j = *i; // assign value *i to *j ++i; // increment i ++j; // increment j }
There is no purpose served (other than explanatory) by declaring extra local variables in the second version, and the postfix increment calls in the first version effect the post-assignment increment of both iterators in the second version.
This template function can be invoked whenever containers have iterators with the operations *() and ++(int) defined. Thus we can invoke the function for Vector, List, Deque, or ordinary arrays (whose pointers satisfy the same conditions). Given that we have declarations
Vector <sometype> V; // a vector object List <sometype> L; // a list object Deque <sometype> D; // a deque object sometype * A[size]; // an array
and all containers have size size, the function can be invoked to copy elements from any one of the containers to another, as in the following examples:
g_copy (V.Begin(), V.End(), L.Begin()); // copies V to L g_copy (L.Begin(), L.End(), D.Begin()); // copies L to D g_copy (D.Begin(), D.End(), A); // copies D to A g_copy (A, A + size, L.Begin()); // copies A to L
The actual code for g_copy() is re-used across all four container types.
This is the classic sequential search algorithm introduced in Chapter 7, converted into a generic algorithm. The input to the algorithm consists of iterarors defining a range of values. The algorithm searches through the range sequentially for a match to t. As soon as success is achieved an iterator pointing to the location of t is returned. Return of the End() iterator (generally an invalid iterator) indicates failure of the search.
The generic find algorithm can be invoked for any range in a container whose iterators have operators *() and ++(). Assume the following container declarations:
Vector <sometype> V; // a vector object List <sometype> L; // a list object Deque <sometype> D; // a deque object sometype A[size]; // an array
and the following iterator declarations:
Vector <sometype>::Iterator Vitr; // a vector iterator List <sometype>::Iterator Litr; // a list iterator Deque <sometype>::Iterator Ditr; // a deque iterator sometype * Aitr; // a pointer (array iterator)
Then generic find can be invoked as follows:
Litr = g_find (L.Begin(), L.End(), t) // location of t in List L Vitr = g_find (V.Begin(), V.End(), t) // location of t in Vector V Ditr = g_find (D.Begin(), D.End(), t) // location of t in Deque D Aitr = g_find (A, A + size, t) // location of t in array A
This algorithm, whose implementation is shown on the slide, returns the location of the largest element of a range in a container (the first largest, if there is more than one). The range is given by a begin/end iterator pair, and the location is returned also as an iterator. The code for this implementation is equivalent to the following more readable, less compact version:
template <class I> I g_max_element (I beg, I end) { if (beg == end) return end; I max; max = beg; ++beg; while (beg != end) { if (*max < *beg) max = beg; ++beg; } return max; }
This generic algorithm can be invoked for any range in a container whose iterators have operators *() and ++(). Assume the container declarations as above and the following iterator declarations:
Vector <sometype>::Iterator Vitr; // a vector iterator List <sometype>::Iterator Litr; // a list iterator Deque <sometype>::Iterator Ditr; // a deque iterator sometype * Aitr; // a pointer (array iterator)
Then generic max can be invoked as follows:
Vitr = g_max_element (V.Begin(), V.End()) // max element of V = *Vitr Litr = g_max_element (L.Begin(), L.End()) // max element of L = *Litr Ditr = g_max_element (D.Begin(), D.End()) // max element of D = *Ditr Aitr = g_max_element (A, A + size) // max element of A = *Aitr
Again, the actual code for the generic algorithm is re-used for four different
kinds of containers
Function objects are used extensively by generic algorithms. Predicate objects, in particular, are used when non-default boolean operations are needed. For example, there is an alternate form of generic max that imports a user-definable notion of order:
template <class I, class P> I g_max_element (I beg, I end, const P& p) { if (beg == end) return end; I max(beg); while (++beg != end) if (p(*max, *beg)) max = beg; return max; }
A predicate object P p is passed to the algorithm and used in place of operator <() to determine the maximum value in the incoming range. The following are example uses of this version:
TGreaterThan <sometype> gt; Vitr = g_max_element (V.Begin(), V.End(), gt) // min element of V = *Vitr Litr = g_max_element (L.Begin(), L.End(), gt) // min element of L = *Litr Ditr = g_max_element (D.Begin(), D.End(), gt) // min element of D = *Ditr Aitr = g_max_element (A, A + size, gt) // min element of A = *Aitr
Note that this invocation actually finds the minimum value, because of the
substitution of greater-than for less-than.
Non-boolean function objects are also used in some algorithms. One of the most used is g_for_each, shown in the slide, which applies a function object to every element in a specified range.
Generic For Each is an algorithm that applies a function object to each element in a range.
template <class I, class F> F g_for_each (I beg, I end, F f) { while (beg != end) f(*(beg++)); return f; }
The function class F may be anything that may be applied to elements of type t. It could count things, accumulate a value over the range (sum or product, for example), or modify all the objects in the range. Here is an example that conditionally modifies the elements in a range, using the MakeUpperCase function class from the previous chapter:
List<char> L; // ... code putting elements into L MakeUpperCase muc; g_for_each (L.Begin(), L.End(), muc); // converts every element of L to upper case
After the call to g_for_each, all alpha characters in L are
upper case.
The generic algorithm g_for_each, introduced in the previous slide, is unusual in that it has a return value: a copy of the function object passed into the algorithm is returned as a value. For stateless function objects this has little utility, but for function objects that modify their own data as they are used, this can be very useful.
A "smart" version of MakeUpperCase is shown in this slide; SmartMakeUpperCase objects keep count of the number of times a letter is converted from lower to upper case. By returning a copy of this function object, a running count of the number of lower case letters converted to upper case is preserved. This example is somewhat contrived, but it illustrates some of the power of "smart" functions and how the generic for each algorithm can facilitate their utility.
Here is another example that accumulates values over a range. This function class is defined as a template class so that it may be applied to any type for which operator +() is defined.
template <typename T> class Addemup { public: T sum; Addemup (const T& init) : sum(init) {} void operator () (const T& t) { sum += t; } };
List<float> L; // ... code putting elements into L Addemupadder(0); adder = g_for_each (L.Begin(), L.End(), adder); std::cout << adder.sum;
Note that
The result output to screen is the sum of all elements in the range.
Testing Generic Algorithms
/* fga.cpp functionality test of generic algorithms using List<T>, Vector<T>, Deque<T>, and arrays of T */ typedef char ElementType; const char* e_t = "char"; List < ElementType > L; // a list Vector < ElementType > V; // a vector Deque < ElementType > Q; // a deque ElementType * A; // an array List < ElementType >::Iterator Litr, Lloc; Vector < ElementType >::Iterator Vitr, Vloc; Deque < ElementType >::Iterator Qitr, Qloc; ElementType * Aitr, * Aloc; ElementType e, s; // entry, sentinal unsigned int i, p, size; // define the list L somehow, and properly size all containers // ... // copy to vector, deque, and array g_copy (L.Begin(), L.End(), V.Begin()); g_copy (V.Begin(), V.End(), Q.Begin()); g_copy (Q.Begin(), Q.End(), A); // ... // g_min_element Lloc = g_min_element(L.Begin(), L.End()); Vloc = g_min_element(V.Begin(), V.End()); Qloc = g_min_element(Q.Begin(), Q.End()); Aloc = g_min_element(A, A + size); // ... // g_max_element Lloc = g_max_element(L.Begin(), L.End()); Vloc = g_max_element(V.Begin(), V.End()); Qloc = g_max_element(Q.Begin(), Q.End()); Aloc = g_max_element(A, A + size); // g_find (sequential search each container for value e): Lloc = g_find (L.Begin(), L.End(), e); Vloc = g_find (V.Begin(), V.End(), e); Qloc = g_find (Q.Begin(), Q.End(), e); Aloc = g_find (A, A + size, e); // g_for_each (apply a SmartMakeUpperCase object to each element of each container): SmartMakeUpperCase smuc; smuc = g_for_each(L.Begin(), L.End(), smuc); smuc = g_for_each(V.Begin(), V.End(), smuc); smuc = g_for_each(Q.Begin(), Q.End(), smuc); smuc g_for_each(A, A + size, smuc); // output the number of conversions from lower case to upper case: cout << "Number of case conversions: " << smuc.count;
The following is taken from a test/demonstration program that calls various generic algorithms for four different container classes.
/* fga.cpp functionality test of generic algorithms using List<T>, Vector<T>, Deque<T>, and arrays of T */ typedef char ElementType; const char* e_t = "char"; List < ElementType > L; // a list Vector < ElementType > V; // a vector Deque < ElementType > Q; // a deque ElementType * A; // an array List < ElementType >::Iterator Litr, Lloc; Vector < ElementType >::Iterator Vitr, Vloc; Deque < ElementType >::Iterator Qitr, Qloc; ElementType * Aitr, * Aloc; ElementType e, s; // entry, sentinal unsigned int i, p, size; // define the list L somehow, and properly size all containers // ... // copy to vector, deque, and array g_copy (L.Begin(), L.End(), V.Begin()); g_copy (V.Begin(), V.End(), Q.Begin()); g_copy (Q.Begin(), Q.End(), A); // ... // g_min_element Lloc = g_min_element(L.Begin(), L.End()); Vloc = g_min_element(V.Begin(), V.End()); Qloc = g_min_element(Q.Begin(), Q.End()); Aloc = g_min_element(A, A + size); // ... // g_max_element Lloc = g_max_element(L.Begin(), L.End()); Vloc = g_max_element(V.Begin(), V.End()); Qloc = g_max_element(Q.Begin(), Q.End()); Aloc = g_max_element(A, A + size); // g_find (sequential search each container for value e): Lloc = g_find (L.Begin(), L.End(), e); Vloc = g_find (V.Begin(), V.End(), e); Qloc = g_find (Q.Begin(), Q.End(), e); Aloc = g_find (A, A + size, e); // g_for_each (apply a SmartMakeUpperCase object to each element of each container): SmartMakeUpperCase smuc; smuc = g_for_each(L.Begin(), L.End(), smuc); smuc = g_for_each(V.Begin(), V.End(), smuc); smuc = g_for_each(Q.Begin(), Q.End(), smuc); smuc g_for_each(A, A + size, smuc); // output the number of conversions from lower case to upper case: cout << "Number of case conversions: " << smuc.count;
The source code for this demonstration program is distributed in file
tests/fga.cpp. An executable version is also available in
area51/fga.x.
A simple generic insertion sort algorithm is presented in two versions, one using the default order determined by int operator < (const T& ,const T& ) and the other taking an order predicate class in through the template parameter.
template < class BidirectionalIterator > void g_insertion_sort (BidirectionalIterator beg, BidirectionalIterator end) { BidirectionalIterator I, J, K; typename BidirectionalIterator::ValueType t; for (I = beg; I != end; ++I) { t = *I; for (K = I, J = K--; J != beg && t < *K; --J, --K) *J = *K; *J = t; } } template < class BidirectionalIterator, class Comparator > void g_insertion_sort (BidirectionalIterator beg, BidirectionalIterator end, const Comparator& cmp) { BidirectionalIterator I, J, K; typename BidirectionalIterator::ValueType t; for (I = beg; I != end; ++I) { t = *I; for (K = I, J = K--; J != beg && cmp(t,*K); --J, --K) *J = *K; *J = t; } }
Here are sample uses of both these versions of insertion sort:
List<int> L; ... g_insertion_sort(L.Begin(), L.End()); // sorts in increasing order g_insertion_sort(L.Begin(), L.End(), GreaterThan); // sorts in decreasing order
One interesting point illustrated by g_insertion_sort is the way the value type of the range is needed and accessed. For this reason, these algorithms will not compile for raw pointers. To make them work for arrays, we need to specialize the templates, as follows:
// specialization for pointers template < typename T > void g_insertion_sort (T* beg, T* end) { T *I, *J, *K; T t; for (I = beg; I != end; ++I) { t = *I; for (K = I, J = K--; J != beg && t < *K; --J, --K) *J = *K; *J = t; } } // specialization for pointers template < typename T , class Comparator > void g_insertion_sort (T* beg, T* end, const Comparator& cmp) { T *I, *J, *K; T t; for (I = beg; I != end; ++I) { t = *I; for (K = I, J = K--; J != beg && cmp(t,*K); --J, --K) *J = *K; *J = t; } }
Now one can use the same generic algorithm for vectors and arrays, as in:
Vector<int> V; int A[size]; ... g_insertion_sort(V.Begin(), V.End()); // sorts vector V g_insertion_sort(A, A + size); // sorts array A
The technique relies on the feature of C++ that allows specializations of
templates. When the compiler encounters more than one way to instantiate a
template, it chooses the most specific. Thus, when we apply generic insertion
sort to a range specified by pointers, there are two possible templates that
fir. The specialization is the more specific, so it is chosen.
There are six versions of binary search available as generic algorithms: two versions each of lower_bound, upper_bound, and binary_search. One version of each uses standard less_than order and one uses a predicate object to determine order. These algorithms require random access iterators (operator [] () and "pointer" arithmetic) and also require that the range of search be ordered (either by operator <() or by the predicate object passed in as a parameter).
The predicate version of lower_bound is shown in the slide. It is important to understand this implementation in detail, including:
All six versions of generic binary search are distributed in
tcpp/gbsearch.h. Headers are presented at the end of this chapter.
/* fgss.cpp "generic sort & search" functionality test of generic sort and binary search on Vector<T>, Deque<T>, and arrays of T */ #include <iostream.h> #include <vector.cpp> #include <deque.cpp> #include <compare.cpp> #include <gsort.h> #include <gbsearch.h> #include <genalg.h> int main() { typedef char ElementType; const char* e_t = "char"; typedef TLessThan <ElementType> PredicateType1; typedef TGreaterThan <ElementType> PredicateType2; Vector < ElementType > V; // a vector Deque < ElementType > Q; // a deque ElementType * A; // an array PredicateType1 LT; // predicate object PredicateType2 GT; // predicate object Vector < ElementType > ::Iterator Vitr, Vloc; Deque < ElementType > ::Iterator Qitr, Qloc; ElementType * Aitr, * Aloc; ElementType e, s; // entry, sentinal unsigned int i, size; cout << "Begin test of g_insertion_sort and g_binary_search < " << e_t << " >\n" << "Enter sentinal: "; cin >> s; cout << "Enter elements ('" << s << "' to end): "; cin >> e; while (e != s) { V.PushBack(e); cin >> e; } cout << "V as entered: " << V << '\n'; // copy to deque size = V.Size(); for (i = 0; i < size; ++i) { if (i%2) Q.PushFront(V[i]); else Q.PushBack(V[i]); } cout << "Q as copied: " << Q << '\n'; // copy to array A = new ElementType [size]; for (i = 0; i < size; ++i) A[i] = V[i]; cout << "A as copied: "; for (i = 0; i < size; ++i) cout << A[i]; cout << '\n'; // apply generic insertion sort to each container, display, and recopy g_insertion_sort(V.Begin(), V.End(), GT); cout << "g_insertion_sort(V,>): " << V << '\n'; g_copy(Q.Begin(), Q.End(), V.Begin()); g_insertion_sort(Q.Begin(), Q.End(), GT); cout << "g_insertion_sort(Q,>): " << Q << '\n'; g_copy(V.Begin(), V.End(), Q.Begin()); g_insertion_sort(A, A + size, GT); cout << "g_insertion_sort(A,>): "; for (i = 0; i < size; ++i) cout << A[i]; cout << '\n'; g_copy(Q.Begin(), Q.End(), A); cout << "V as recopied: " << V << '\n'; cout << "Q as recopied: " << Q << '\n'; cout << "A as recopied: "; for (i = 0; i < size; ++i) cout << A[i]; cout << '\n'; // apply generic insertion sort again to each container and display g_insertion_sort(V.Begin(), V.End()); cout << "g_insertion_sort(V): " << V << '\n'; g_insertion_sort(Q.Begin(), Q.End()); cout << "g_insertion_sort(Q): " << Q << '\n'; g_insertion_sort(A, A + size, LT); cout << "g_insertion_sort(A,<): "; for (i = 0; i < size; ++i) cout << A[i]; cout << '\n'; // now do binary search while(1) { cout << "Enter search value ('" << s << "' to quit): "; cin >> e; if (e == s) break; Vloc = g_lower_bound(V.Begin(), V.End(), e, LT); cout << "V = " << V << '\n'; cout << " "; for (Vitr = V.Begin(); Vitr != Vloc; ++Vitr) cout << ' '; cout << "^lb\n"; Vloc = g_upper_bound(V.Begin(), V.End(), e, LT); cout << "V = " << V << '\n'; cout << " "; for (Vitr = V.Begin(); Vitr != Vloc; ++Vitr) cout << ' '; cout << "^ub\n"; Qloc = g_lower_bound(Q.Begin(), Q.End(), e, LT); cout << "Q = " << Q << '\n'; cout << " "; for (Qitr = Q.Begin(); Qitr != Qloc; ++Qitr) cout << ' '; cout << "^lb\n"; Qloc = g_upper_bound(Q.Begin(), Q.End(), e, LT); cout << "Q = " << Q << '\n'; cout << " "; for (Qitr = Q.Begin(); Qitr != Qloc; ++Qitr) cout << ' '; cout << "^ub\n"; Aloc = g_lower_bound(A, A + size, e, LT); cout << "A = "; for (Aitr = A; Aitr != A + size; ++Aitr) cout << *Aitr; cout << '\n'; cout << " "; for (Aitr = A; Aitr != Aloc; ++Aitr) cout << ' '; cout << "^lb\n"; Aloc = g_upper_bound(A, A + size, e, LT); cout << "A = "; for (Aitr = A; Aitr != A + size; ++Aitr) cout << *Aitr; cout << '\n'; cout << " "; for (Aitr = A; Aitr != Aloc; ++Aitr) cout << ' '; cout << "^ub\n"; cout << e << " is "; if (!g_binary_search(V.Begin(), V.End(), e, LT)) cout << "not "; cout << "in V\n"; cout << e << " is "; if (!g_binary_search(Q.Begin(), Q.End(), e, LT)) cout << "not "; cout << "in Q\n"; cout << e << " is "; if (!g_binary_search(A, A + size, e, LT)) cout << "not "; cout << "in A\n"; } cout << "End test of g_insertion_sort/g_binary_search < " << e_t << " >\n"; return 0; }
(See tests/fgss.cpp.)
Generic algorithms fall into functional categories differentiated by their assumptions and runtime characteristics and are located in separate files by category. The following table shows the categories and the file names (duplicated in the slide):
category | iterator types | container preconditions | file | comments (n = size of input) |
utility | input or output | none | genalg.h | sequential (iterative) algorithms O(n) |
insertion sort | bidirectional | none | gsort.h | in place, stable, O(n2) |
set algorithms | input or output | totally ordered (sorted) | gset.h | sequential (iterative) set algorithms O(n) |
binary search | random access | totally ordered (sorted) | gbsearch.h | O(log n) |
heap sort | random access | none | gheap.h | in place, unstable, O(n log n) |
heap algorithms | random access | partially ordered (POT) | gheap.h | push and pop heap O(log n) |
The following is a listing of all of the generic algorithms in these files, by category. Namespaces are not shown.
/* genalg.h General utility generic algorithms for non-random-access iterators I, J iterator classes (usually I = input iterator, J = output) T ValueType P predicate class (may be unary or binary, depending on context) F function class (context determined) */ template <class I, typename T> void g_fill (I beg, I end, const T& t) template <class I, typename T> void g_fill_n (I beg, int n, const T& t) template <class I, typename T> void g_fill_n (I beg, unsigned int n, const T& t) template <class I, class J> void g_copy (I source_beg, I source_end, J dest_beg) template <class I, class J> void g_copy_backward (I source_beg, I source_end, J dest_beg) template <class I> I g_min_element (I beg, I end) template <class I, class P> I g_min_element (I beg, I end, const P& p) template <class I> I g_max_element (I beg, I end) template <class I, class P> I g_max_element (I beg, I end, const P& p) template <class I, typename T> I g_find (I beg, I end, const T& t) template <class I, class P> I g_find_if (I beg, I end, const P& p) template <class I, class F> F g_for_each(I beg, I end, F f)
/* gsort.h generic sort algorithm applicable to a range specified by iterators implementing insertion sort algoriithm */ template < class BidirectionalIterator > void g_insertion_sort (BidirectionalIterator beg, BidirectionalIterator end); template < class BidirectionalIterator, class Comparator > void g_insertion_sort (BidirectionalIterator beg, BidirectionalIterator end, const Comparator& cmp); // specialization for pointers template < typename T > void g_insertion_sort (T* beg, T* end); // specialization for pointers template < typename T , class Comparator > void g_insertion_sort (T* beg, T* end, const Comparator& cmp);
/* gset.h generic set algorithms All of these algorithms have a common set of preconditions, as follows: 1. I1, I2 are input iterators and I3 is output iterator. (I3 may be an insert iterator.) 2. P is a predicate order class defined on the input ranges (transitive, anti-reflexive, and dichotomous) 3. The two input ranges are sorted using p where present and < otherwise 4. The input range ValueType is convertible to the output range ValueType A common postcondition is: the output range is sorted using p where present or < otherise */ template <class I1, class I2, class I3, class P> void g_set_union(I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest, P p) // range3 = range1 union range2 template <class I1, class I2, class I3> void g_set_union(I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest) // default order version uses operator < template <class I1, class I2, class I3, class P> void g_set_merge (I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest, const P& p) // range3 = range1 merge range2 template <class I1, class I2, class I3> void g_set_merge (I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest) // default order version uses operator < () template <class I1, class I2, class I3, class P> void g_set_intersection(I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest, const P& p) // range3 = range1 intersection range2 template <class I1, class I2, class I3> void g_set_intersection(I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest) // default order version uses operator <() template <class I1, class I2, class I3, class P> void g_set_difference(I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest, const P& p) // range3 = range1 difference range2 template <class I1, class I2, class I3> void g_set_difference(I1 beg1, I1 end1, I2 beg2, I2 end2, I3 dest) // default order version uses operator <() template <class I1, class I2, class P> int g_subset_of(I1 beg1, I1 end1, I2 beg2, I2 end2, P p) // returns true iff range 1 is contained in range 2 template <class I1, class I2> int g_subset_of (I1 beg1, I1 end1, I2 beg2, I2 end2) // default version uses operator <()
/* gbsearch.h Generic binary search algorithms for random access iterators These algrithms take parameters of type I, T, and optionally a third parameter of type P. about I ======= I is a random access iterator type; this means that the bracket operator T& operator [] (unsigned int); is defined and the usual "pointer" arithmetic operations are defined: I& operator += (long n); I& operator -= (long n); I operator + (long n) const; long operator - (const I& I2) const; Examples include: ordinary array iterators (pointers); Vector<T>::Iterator; Dequeue<T>::Iterator; about T ======= T is the ValueType of the iterator I about P ======= P is a predicate class used for order; an object LessThan of class P has the function evaluation operator overloaded with the prototype bool operator () (const T&, const T&); Note that P must be used to order the elements so that P can work as a search condition. g_lower_bound() and g_upper_bound() implement binary search for random access iterators. It is assumed that the elements of iteration are in nondecreasing order (using operator < or an optional predicate object). g_lower_bound returns an iterator to the first location containing the search value if found, and the location where it would be otherwise. g_upper_bound returns an iterator to the first location whose element is greater than the search value if such exists or the end iterator otherwise. I.e. (assuming L = lower bound and U = upper bound): if t is in the collection: t == *L, t < *U, and L and U are the smallest iterators in the sequence with these properties; thus, beg = L and end = U mark the range of t in the collection; if t is not in the collection: L == U, and these iterators point to the location where t could be inserted and maintain nondecreasing order. bool binary_search() uses the same algorithm, returning true iff found. All these versions of binary search run in time O(log size). */ #include <compare.cpp> template <class I, typename T, class P> I g_lower_bound (I beg, I end, const T& val, const P& LessThan) // pre: I is a random access iterator (operator [] and "pointer" arithmetic) // I has ValueType T // beg + n = end for some n >= 0 // beg[0] ... beg[n-1] are in non-decreasing order using LessThan // post: no state is changed // return: itr = beg + i, where beg[i-1] < val <= beg[i]; or // itr = end if no such i exists template <class I, typename T, class P> I g_upper_bound (I beg, I end, const T& val, const P& LessThan) // pre: I is a random access iterator (operator [] and "pointer" arithmetic) // I has ValueType T // beg + n = end for some n >= 0 // beg[0] ... beg[n-1] are in non-decreasing order using LessThan // post: no state is changed // return: itr = beg + i, where beg[i-1] <= val < beg[i]; or // itr = end if no such i exists template <class I, typename T, class P> int g_binary_search (I beg, I end, const T& val, const P& LessThan) // pre: I is a random access iterator (operator [] and "pointer" arithmetic) // I has ValueType T // beg + n = end for some n >= 0 // beg[0] ... beg[n-1] are in non-decreasing order using LessThan // post: no state is changed // return: true if found, false if not found template <class I, typename T> I g_lower_bound (I beg, I end, const T& val) template <class I, typename T> I g_upper_bound (I beg, I end, const T& val) template <class I, typename T> int g_binary_search (I beg, I end, const T& val)
/* gheap.h The generic heap algorithms These algorithms use three types I, P, T but only two template parameters <I,P> I: a random access iterator class P: a predicate class for T T: the ValueType for underlying container on which I iterates A random access iterator I is one for which the bracket operator T& operator [] (unsigned int ); is defined and for which "pointer" arithmetic is defined. Examples include: ordinary arrays of T; Vector<T>; and Deque<T> A predicate class is one for which the function evaluation operator bool operator () (const T&, const T&); is defined. An object LessThan of class P is used to determine order in T: "LessThan (a,b) == true" iff "a < b" The type T is not explicitly mentioned in the template (or parameter) list for g_heap_sort, but it is needed for the call to g_XC. */ #include <compare.cpp> template <class I, class P> void g_heap_sort (I beg, I end, const P& LessThan) // Implements heapsort for the iterators // Heapsort has the following attributes: // * fast: runtime O(size*log(size)) // * in-place: the sort happens in the space implied by the iterators // * NOT stable -- the original order of objects a, b may change when // a == b // pre: I is a random access iterator class (operator [] and "pointer" arithmetic) // T is the ValueType of I // P is a predicate class for type T // post: the range of values specified by the iterators is ordered LessThan, // i.e., LessThan(a,b) == true ==> a comes before b in the container template <class I> void g_heap_sort (I beg, I end) // the default order version template <class I, class P> void g_push_heap (I beg, I end, const P& LessThan) // Defn: a < b means LessThan(a,b) == true // Defn: POT means parent >= children // Pre: the range [beg,end) is valid // if the range [beg,end-1) is valid (i.e., [beg,end) is non-void) // then the range [beg,end-1) is a POT // Post: the range [beg,end) is a POT template <class I> void g_push_heap (I beg, I end) // the default order version template <class I, class P> void g_pop_heap (I beg, I end, const P& LessThan) // Defn: a < b means LessThan(a,b) == true // Defn: POT means parent >= children // Pre: the range [beg,end) is valid // the range [beg,end-1) is valid (i.e., [beg,end) is non-void) // the range [beg,end) is a POT // Post: the largest element beg[0] is removed from the range [beg,end-1) // the range [beg,end-1) is a POT // the old largest element (i.e., beg[0]) is copied to // the end of the old range (i.e., to beg[end - beg - 1]) template <class I> void g_pop_heap (I beg, I end) // the default order version template <typename T> void g_XC (T& t1, T& t2)