CS 411 Fall 2025 > Outline & Supplemental Notes for September 24, 2025
CS 411 Fall 2025
Outline & Supplemental Notes
for September 24, 2025
Outline
Decrease by a Constant Factor [L 4.4]
- The idea
- If there is a constant amount of extra work in each function call, then this strategy gives us a \(O(\log n)\) algorithm.
- Binary Search is an example of this.
- Binary Search
- Important algorithm; simple to describe.
- Implementation issues (see Supplemental Notes)
- Fake-Coin Problem (not covered in class)
- Russian Peasant Multiplication
- This is decrease by a constant factor, if we consider the size of the problem to be the value of the lower number.
- Analysis
- Choose basic op to be anything from inside loop.
- Get \(\Theta(\log n)\) for \(n\) a power of \(2\).
- Smoothness Rule gives us \(\Theta(\log n)\) for all \(n\).
Supplemental Notes
Binary Search: Implementation Issues
Extra Comparison?
A common question when implementing Binary Search is whether we should do an extra comparison to see if the middle item in the sequence is exactly the value we are searching for, and if so, end there.
Certainly we could do this. If we did, then the algorithm would still be logarithmic-time, and it would be faster—constant time!—for some instances. However, exceptional speed-ups would be rare, and in the worst case, the number of comparisons would double. Therefore, I generally avoid doing the extra comparison.
Tail Recursion Elimination
The last thing Binary Search does is its recursive call, whose return value it returns. This is called tail recursion. Tail-recursive calls can be transformed easily into efficient loops.
This illustrates a common phenomenon: we often describe algorithms recursively, but we implement them iteratively when we can. Sometimes we do this transformation only in part; when we discuss Quicksort, we will see that a natural implementation has two recursive calls, but we usually eliminate one of these, leaving one.
Generality
If we are careful when implementing Binary Search, we can make it work for a larger class of datasets than we might think at first glance.
Replacing the base-case equality check with one using
the “<
” operator
allows our function to be used with value types
that do not have
the “==
” operator defined.
We replace
[C++]
return *first == findme;
with
[C++]
return !(*first < findme) && !(findme < *first);
The first condition above is equality. The one we replace it with is equivalence.
We can also allow our function to be used with iterators that are not random-access.
We replace
[C++]
size_t size = last - first;
with
[C++]
size_t size = std::distance(first, last);
We similarly replace
[C++]
FDIter middle = first + size/2;
with
[C++]
FDIter middle = std::next(first, size/2);
For both of these changes, the initial code works only with random-access iterators, while the final code works with the more general category of forward iterators, while still remaining fast for random-access iterators.