http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=binarySearch
Binary Search
What we can call the main theorem states that binary search can be used if and only if for all x in S, p(x) implies p(y) for all y > x. This property is what we use when we discard the second half of the search space. It is equivalent to saying that ¬p(x) implies ¬p(y) for all y < x (the symbol ¬ denotes the logical not operator), which is what we use when we discard the first half of the search space.
If the condition in the main theorem is satisfied, we can use binary search to find the smallest legal solution, i.e. the smallest x for which p(x) is true. The first part of devising a solution based on binary search is designing a predicate which can be evaluated and for which it makes sense to use binary search: we need to choose what the algorithm should find. We can have it find either the first x for which p(x) is true or the last x for which p(x) is false. ... The second part is proving that binary search can be applied to the predicate. This is where we use the main theorem, verifying that the conditions laid out in the theorem are satisfied.
... many problems can't be modeled as searching for a particular value, but it's possible to define and evaluate a predicate such as"Is there an assignment which costs x or less?", when we're looking for some sort of assignment with the lowest cost. ... This is called reducing the original problem to a decision (yes/no) problem.
One important thing to remember before beginning to code is to settle on what the two numbers you maintain (lower and upper bound) mean. A likely answer is a closed interval which surely contains the first x for which p(x) is true. All of your code should then be directed at maintaining this invariant: it tells you how to properly move the bounds, which is where a bug can easily find its way in your code, if you're not careful.
... Keep your eye out for overflow errors all around, especially in calculating the median.
binary_search(lo, hi, p):
while lo < hi:
mid = lo + (hi-lo)/2
if p(mid) == true:
hi = mid
else:
lo = mid+1
if p(lo) == false:
complain // p(x) is false for all x in S!
return lo // lo is the least x for which p(x) is true
If we wanted to find the last x for which p(x) is false, we would devise (using a similar rationale as above) something like:
WARNING: Mind the BUG below!
// warning: there is a nasty bug in this snippet!
//The solution is to change mid = lo + (hi-lo)/2 to mid = lo + (hi-lo+1)/2, i.e. so that it rounds up instead of down.
binary_search(lo, hi, p):
while lo < hi:
mid = lo + (hi-lo)/2 // note: division truncates
if p(mid) == true:
hi = mid-1
else:
lo = mid
if p(lo) == true:
complain // p(x) is true for all x in S!
return lo // lo is the greatest x for which p(x) is false
Just remember to always test your code on a two-element set where the predicate is false for the first element and true for the second.
You may also wonder as to why mid is calculated using mid = lo + (hi-lo)/2 ... Coding the calculation this way ensures that the number divided is always positive and hence always rounds as we want it to.
SUMMARY
- Design a predicate which can be efficiently evaluated and so that binary search can be applied
- Decide on what you're looking for and code so that the search space always contains that (if it exists)
- If the search space consists only of integers, test your algorithm on a two-element set to be sure it doesn't lock up
- Verify that the lower and upper bounds are not overly constrained: it's usually better to relax them as long as it doesn't break the predicate
TopCoder Problems
Simple
AutoLoan - SRM 258
SortEstimate - SRM 230
Moderate
UnionOfIntervals - SRM 277
Mortgage - SRM 189
FairWorkload - SRM 169 (Example used in tutorial.Solution available.)
HairCuts - SRM 261
Harder
PackingShapes - SRM 270
RemoteRover - SRM 235
NegativePhotoresist - SRM 210
WorldPeace - SRM 204
UnitsMoving - SRM 278
Parking - SRM 236
SquareFree - SRM 190
Flags - SRM 147