2.2. Searching in an Array¶
2.2.1. Sequential Search¶
If you want to find the position in an unsorted array of \(n\) objects that stores a particular value, you cannot really do better than simply looking through the array from the beginning and move toward the end until you find what you are looking for. This algorithm is called sequential search. If you do find it, we call this a successful search. If the value is not in the array, eventually you will reach the end. We will call this an unsuccessful search. Here is a simple implementation for sequential search.
// Return the position of an element in a list.
// If the element is not found, return -1.
public static<E> int sequentialSearch(E[] elements, E e) {
for (int i = 0; i < elements.length; i++) { // For each element
if (elements[i].equals(e)) // if we found it
return i; // return its position
}
return -1; // Otherwise, return -1
}
# Return the position of an element in a list.
# If the element is not found, return -1.
def sequentialSearch(elements, e):
for i in range(len(elements)): # For each element
if elements[i] == e: # if we found it
return i # return this position
return -1 # Otherwise, return -1
It is natural to ask how long a program or algorithm will take to run. But we do not really care exactly how long a particular program will run on a particular computer. We just want some sort of estimate that will let us compare one approach to solving a problem with another. This is the basic idea of algorithm analysis. In the case of sequential search, it is easy to see that if the value is in position \(i\) of the array, then sequential search will look at \(i\) values to find it. If the value is not in the array at all, then we must look at \(n\) values if the array holds \(n\) values. This would be called the worst case for sequential search. Since the amount of work is proportional to \(n\), we say that the worst case for sequential search has linear cost. For this reason, the sequential search algorithm is sometimes called linear search.
2.2.2. Binary Search¶
Sequential search is the best that we can do when trying to find a value in an unsorted array. 1 But if the array is sorted in increasing order by value, then we can do much better. We use a process called binary search.
Binary search begins by examining the value in the middle position of the array; call this position \(mid\) and the corresponding value \(k_{mid}\). If \(k_{mid} = K\), then processing can stop immediately. This is unlikely to be the case, however. Fortunately, knowing the middle value provides useful information that can help guide the search process. In particular, if \(k_{mid} > K\), then you know that the value \(K\) cannot appear in the array at any position greater than \(mid\). Thus, you can eliminate future search in the upper half of the array. Conversely, if \(k_{mid} < K\), then you know that you can ignore all positions in the array less than \(mid\). Either way, half of the positions are eliminated from further consideration. Binary search next looks at the middle position in that part of the array where value \(K\) may exist. The value at this position again allows us to eliminate half of the remaining positions from consideration. This process repeats until either the desired value is found, or there are no positions remaining in the array that might contain the value \(K\). Here is an illustration of the binary search method.
And here is the method in more programming languages:
// Return the position of an element in a list.
// If the element is not found, return -1.
public static <E extends Comparable<E>> int binarySearch(E[] elements, E e) {
int low = 0;
int high = elements.length - 1;
while (low <= high) { // Stop when low and high meet
int mid = (low + high) / 2; // Check middle of subarray
if (elements[mid].compareTo(e) < 0) {
low = mid + 1; // In right half
} else if (elements[mid].compareTo(e) > 0) {
high = mid - 1; // In left half
} else {
return mid; // Found it
}
}
return -1; // Search value not in array
}
# Return the position of an element in a list.
# If the element is not found, return -1.
def binarySearch(elements, e):
low = 0
high = len(elements) - 1
while low <= high: # Stop when low and high meet
mid = (low + high) // 2 # Check middle of subarray
if elements[mid] < e:
low = mid + 1 # In right half
elif elements[mid] > e:
high = mid - 1 # In left half
else:
return mid # Found it
return -1 # Search value not in array
With the right math techniques, it is not too hard to show that the cost of binary search on an array of \(n\) values is at most \(\log_2 n\). This is because we are repeatedly splitting the size of the subarray that we must look at in half. We stop (in the worst case) when we reach a subarray of size 1. And we can only cut the value of \(n\) in half \(\log_2 n\) times before we reach 1. 2
- 1
It seems to be really “obvious” that sequential search is the best that you can do on an unsorted array. But writing a convincing proof that no algorithm could ever be discovered that is better is surprisingly difficult. This is an example of a lower bounds proof to find the cost for the best possible algorithm to solve the problem of search in an unsorted array.
- 2
It is possible to prove that binary search is the most efficient algorithm possible in the worst case when searching in a sorted array. This is even more difficult than proving that sequential search is the most efficient algorithm possible on an unsorted array.