How To Write Binary Search Correctly
How To Write Binary Search Correctly
Introduction
Term explanation
Invariant
Bound function
Precondition and postcondtion
Binary search
Problem statement
Implementation
Implementation 1
Implementation 2
Binary search variations
Example 1
Example 2
Conclusion
Reference
Introduction
Binary search is a straightforward algorithm to understand but it is
hard to code it right. This is especially true given the forms of
implementation on binary search can take on many. In addition, there
are many problems can be solved by binary search with slight
https://zhu45.org/posts/2018/Jan/12/how-to-write-binary-search-correctly/ 1/14
7/13/2020 How to write binary search correctly
Term explanation
Invariant
Bound function
When you invoke a function — or, all right, a method — you have a
sense of what needs to be true when you invoke it, and what it
guarantees to be true when it returns. For example, when you call a
function oneMoreThan(val), you undertake to ensure that val is an
integer, and the function undertakes to ensure that the value it
returns is one more than the one you passed in. These two promises
— the precondition and postcondition — constitute the contract of the
function. So:
The precondition is the promise that you make before running a
bit of code;
The postcondition is the promise that the code makes a er it’s
been run.
Binary search
Problem statement
Implementation
The invariant for our binary search algorithm is: "if the target value X
is present in the array, then the target value is present in the current
range." As mentioned above, invariant is formalized using specific
variables and values. Here, we need to decide the representation of
"current range". This is the place where the binary search has many
ways of implementation. We use low and high to define the range and
we use n to denote the length of array. There are several popular
formalization of the "current range":
Number 2 and 4 are the invariants that behind two most popular
implementation of binary search you can find on the internet.
Implementation 1
For 2, the equation means that i ∈ [low, high) . Thus, low is initialized
to 0 and high initialized to n. Thus, the invariant for this is "If X is at
any position i in A (i.e., A = X ) then low <= i < high". The
i
return i
elif X > A[i]:
low = i + 1
else: # X < A[i]
high = i
return -1
The third branch follows the same form as the second: since
we know that X < A[i] and that A[j] >= A[i]∀j > i, we
know the highest position the target can be at is A[i-1].
However, our invariant insists that i ∈ [low, high) with
high being exclusive brace. Thus, we cannot set high to be
i-1 and instead, we set it to i. Doing so, we maintain our
invariant unchanged.
Since we’ve verified that all three branches of the if maintain the
invariant, we know that the invariant holds on exiting that if.
That means the invariant is true at the bottom of the loop, which
means it will be true at the start of the next time around the loop.
And by induction we deduce that it always remains true.
Finally, we break out of the loop when low < high is false,
which means that the candidate range is empty (i.e. low ==
high). At this point, we know that the condition of the invariant
(“If X is at any position i in A”) does not hold, so the invariant is
trivially true; and we return the out-of-band value -1. 1
Implementation 2
For 4, the equation means that i ∈ [low, high]. Thus, low is initialized
to 0 and high initialized to n-1. Thus, the invariant for this is "If X is at
any position i in A (i.e., A = X ) then low <= i <= high". The
i
https://zhu45.org/posts/2018/Jan/12/how-to-write-binary-search-correctly/ 6/14
7/13/2020 How to write binary search correctly
Mike's post has done the similar invariant unchanged analysis, which
I'll skip for this implementation. Until now, we haven't touched on the
concept of "bound function" and "postcondition" in our analysis.
However, that doesn't mean these two concepts are not important.
Usually, "bound function" is used to prove our loop terminates (Mike's
post talks about how to use "bound function" to show above
implementation terminates; TopCoder link gives an example of why we
need to show algorithm actually terminates). Next section, we'll see an
example of checking postcondition is important to make sure we have
correct return result.
Example 1
0,1,2,4,5,6,7
7,0,1,2,4,5,6
6,7,0,1,2,4,5
5,6,7,0,1,2,4
4,5,6,7,0,1,2
2,4,5,6,7,0,1
1,2,4,5,6,7,0
The key observation is that if the middle value is greater than the le
value, then the minimum value appears on the righthand-side of the
middle value (i.e., for [4,5,6,7,0,1,2], the middle value is 7 and the
minimum value 0 appears on the righthand-side of 7). Otherwise, the
https://zhu45.org/posts/2018/Jan/12/how-to-write-binary-search-correctly/ 7/14
7/13/2020 How to write binary search correctly
https://zhu45.org/posts/2018/Jan/12/how-to-write-binary-search-correctly/ 8/14
7/13/2020 How to write binary search correctly
Example 2
The second example we are asked to find the index of the first number
that is greater than the given target number in the array. Like basic
binary search problem, the array is sorted in ascending order. As
always, let's consider some examples for this problem. Suppose we are
given an array [0,1,5,7,8,10,12,15], if the target number is 3, we
should return 2, which is the index of the first number that is greater
than 3 (i.e., 5). What about the target number is 16? In this case, there
is no number in the array is greater than the target number, and we
should return -1.
What's the invariant for this problem? Similar to the other binary
search problem, the invariant is "the index i of the first number that is
greater than the given target number in the array is in [low, high]".
Thus, we can initialize low to be 0 and high to be n. Note that we set
high to n instead of n-1 due to the need to maintain the invariant: for
the case when there is no number in the array that is greater than the
target number, we can think about the first number that is greater than
the target number happens one past the last index. Then, by our
https://zhu45.org/posts/2018/Jan/12/how-to-write-binary-search-correctly/ 9/14
7/13/2020 How to write binary search correctly
holds.
Note
Here is a reasoning why low cannot be greater than high when the
loop exits: suppose that low is greater than high on loop exit. The
only possible case that low is greater than high is when
low = mid + 1 and high = mid where mid means the mid
0 −1 0
value of the current iteration (i.e., immediately before loop exit) and
mid means the mid value of the previous iteration. Then, we want
−1
https://zhu45.org/posts/2018/Jan/12/how-to-write-binary-search-correctly/ 10/14
7/13/2020 How to write binary search correctly
(low
−1
+ mid assuming in the last iteration, low is changed
−1
)/2
mid because otherwise we already exit the while loop. Thus, mid
−1 0
Once we exit the loop, we need to check our postcondition once again.
Our postcondition asks us the index of the first number that is greater
than the target number if it is in the array and -1 otherwise. However,
during the initialization of high, we consider n represents the case
when no such number exists and at the same time, satisfies our
invariant. Thus, before returning the result, we need to check whether
high within the index range of the given array to satisfy the
postcondition constraint.
Conclusion
In this post, we take a look at the technique that helps us implement
the binary search correctly: maintain the invariant. Also, we emphasize
the importance of the postcondition to help us get the returen result
correctly. We haven't empahsized the importance of bound function in
the post but we should consider it as well. There are two ways to check
the loop indeed terminates: one is through reasoning similar what we
have done in invariant analysis (Mike's post and Paul's post give us
examples on how to do that); another way is by considering some
special cases like there are only two elements in the array suggested by
the TopCoder article below.2 Some details are le out in this post: 1.
why use mid = low + (high - low)//2 instead of mid = (low +
high) // 2 3 2. Use A[low] <= A[i] < A[high] as an invariant is
Reference
https://zhu45.org/posts/2018/Jan/12/how-to-write-binary-search-correctly/ 11/14
7/13/2020 How to write binary search correctly
https://zhu45.org/posts/2018/Jan/12/how-to-write-binary-search-correctly/ 14/14
7/13/2020 How to write binary search correctly
ALSO ON ZHU45.ORG
Solutions including: MAW Recently, I start to work on All the source code relates
6.6, 6.7, 6.9, 6.13, 6.14, leetcode's problems. My to this book can be found on
6.16, 6.17, 6.27, 6.28, … goal is to solve two … my git repo I work on …
LOG IN WITH
OR SIGN UP WITH DISQUS ?
Name
The second branch (i.e., X > A[i]) is the first time we need to use non-trivial
reasoning. If we’re in this branch, we know that the condition guarding it was
true, i.e., X < A[i].
Hello Mudit,
Thanks much for the comment. It is indeed a typo and I fixed the post.
Please let me know if you identify additional typos or the place that I can
make the concept clearer. Thanks in advance.
1△ ▽ • Reply • Share ›
For the binary search approaches, did you choose "index of exact match or -1" vs.
"sorted insertion index" for any particular reason like ease of explanation? I tend to
https://zhu45.org/posts/2018/Jan/12/how-to-write-binary-search-correctly/ 13/14