Summary: We consider a typical problem of computing and a variety of algorithms used to solve that problem.
To search a data structure is to examine its elements singly
until one has either found an element that has a desired property or
concluded that the data structure contains no such element. For instance,
one might search a vector of integers for an even element, or a
vector of pairs for a pair having the string
In a linear data structure -- such as a flat list, a vector, or a file -- there is an obvious algorithm for conducting a search: Start at the beginning of the data structure and traverse it, testing each element. Eventually one will either find an element that has the desired property or reach the end of the structure without finding such an element, thus conclusively proving that there is no such element. Here are a few versions of the algorithm.
;;; Procedure: ;;; sequential-search-list ;;; Parameters: ;;; pred?, predicate ;;; lst, a list ;;; Purpose: ;;; Searches the list for a value that matches the predicate. ;;; Produces: ;;; match, a value ;;; Preconditions: ;;; pred? can be applied to all values in lst. ;;; Postconditions: ;;; If lst contains an element for which pred? holds, match ;;; is one such value. ;;; If lst contains no elements for which pred? holds, match ;;; is false (#f). (define sequential-search-list (lambda (pred? lst) (cond ; If the list is empty, no values match the predicate. ((null? lst) #f) ; If the predicate holds on the first value, use that one. ((pred? (car lst)) (car lst)) ; Otherwise, look at the rest of the list (else (sequential-search-list pred? (cdr lst)))))) ;;; Procedure: ;;; sequential-search-vector ;;; Parameters: ;;; pred?, predicate ;;; vec, a vector ;;; Purpose: ;;; Searches the vector for a value that matches the predicate. ;;; Produces: ;;; match, a value ;;; Preconditions: ;;; pred? can be applied to all elements of vec. ;;; Postconditions: ;;; If vec contains an element for which pred? holds, match ;;; is the index of one such value. That is, ;;; (pred? (vector-ref vec match)) holds. ;;; If vec contains no elements for which pred? holds, match ;;; is false (#f). (define sequential-search-vector (lambda (pred? vec) ; Grab the length of the vector so that we don't have to ; keep recomputing it. (let ((len (vector-length vec))) ; Helper: Keeps track of the position we're looking at. (let kernel ((position 0)) ; Start at position 0 (cond ; If we've run out of elements, give up. ((= position len) #f) ; If the current element matches, use it. ((pred? (vector-ref vec position)) position) ; Otherwise, look in the rest of the vector. (else (kernel (+ position 1)))))))) > (define sample (vector 1 3 5 7 8 11 13)) > (sequential-search-vector even? sample) 4 > (sequential-search-vector (right-section = 12) sample) #f
These search procedures return
#f if the search is
unsuccessful. The first returns the matched value if the search is
successful. The second the position in the specified
vector at which the desired element can be found. There are many variants
of this idea: One might, for instance, prefer to signal an error or display
a diagnostic message if a search is unsuccessful. In the case of a
successful search, one might simply return
#t (if all that is
needed is an indication of whether an element having the desired property
is present in or absent from the list).
One of the most common
real-world searching problems is that of
searching a collection compound values for one which matches a particular
portion of the value, known as the key. For example, we might
search a phone book for a phone number using a person's name as the key
or we might search a phone book for a person using the number as key.
As you've probably noted, association lists implement this kind of
searching if we use the first value of a list as the key for that list.
Of course, it is also possible to make a
a parameter to the search procedure.
;;; Procedure: ;;; search-list-for-keyed-value ;;; Parameters: ;;; key, a key to search for. ;;; values, a list of compound values. ;;; get-key, a procedure that extracts a key from a compound value. ;;; Purpose: ;;; Finds a member of the list that has a matching key. ;;; Produces: ;;; A matching value, if found. ;;; #f, otherwise. ;;; Preconditions: ;;; The get-key procedure can be applied to each element of values. ;;; Postconditions: ;;; If the procedure returns #f, there is no value for which ;;; (equal? key (get-key val)) ;;; holds. Otherwise, returns some value for which that holds. (define search-list-for-keyed-value (lambda (key values get-key) (sequential-search-list (lambda (val) (equal? key (get-key val))) values)))
The linear search algorithms just described can be quite slow if the data
structure to be searched is large. If one has a number of searches to
carry out in the same data structure, it is often more efficient to
pre-process the values, sorting them and transferring them to a vector,
before starting those searches. The reason is that one can then use the
much faster binary search algorithm.
Binary search is a more specialized algorithm than linear search. It requires a random-access structure, such as a vector, as opposed to one that offers only sequential access, such as a list. Binary search is limited to the kind of test in which one is looking for a particular value that has a unique relative position in some ordering. For instance, one could use a binary search to look for an element equal to 12 in a vector of integers, since 12 is uniquely located between integers less than 12 and integers greater than 12; but one wouldn't use binary search to look for an even integer, since the even integers don't have a unique position in any natural ordering of the integers.
The idea in a binary search is to divide the sorted vector into two approximately equal parts, examining the element at the point of division to determine which of the parts must contain the value sought. Actually, there are usually three possibilities:
(1) The element at the point of division cannot precede the value sought in the ordering that was used to sort the vector. In this case, the value sought must be in a position with a lower index that the element at the point of division (if it is present at all) -- in other words, it must be in the left half of the vector. The search procedure invokes itself recursively to search just the left half of the vector.
(2) The value sought cannot precede the element at the point of division. In this case, the value sought must be in a higher-indexed position -- in the right half of the vector -- if it is present at all. The search procedure invokes itself recursively to search just the right half of the vector.
(3) The value sought is the element at the point of division. The search has succeeded.
There is one other way in which the recursion can terminate: If, in some recursive call, the subvector to be searched (which will be half of a half of a half of ... of the original vector) contains no elements at all, then the search obviously cannot succeed and the procedure should take the appropriate failure action.
Here, then, is the basic binary-search algorithm. The identifiers
upper-bound denote the starting
and ending positions of the part of the vector within which the value
sought must lie, if it is present at all. (We use the convention that
the starting and ending positions are inclusive in that they
are positions within the vector that we must include in the search.)
;;; Procedure: ;;; binary-search ;;; Parameters: ;;; key, a key we're looking for ;;; vec, a vector to search ;;; get-key, a procedure of one parameter that, given a data item, ;;; returns the key of a data item. ;;; comes-before?, a binary predicate that tells us whether or not ;;; one key may precede another. ;;; Produces: ;;; match, a number. ;;; Preconditions: ;;; The vector is "sorted". That is, ;;; (comes-before? (get-key (vector-ref vec i)) ;;; (get-key (vector-ref vec (+ i 1)))) ;;; holds for all reasonable i. ;;; The comes-before? procedure can be applied to all pairs of keys ;;; in the vector (and to the supplied key) ;;; The comes-before? procedure is transitive. That is, if ;;; (comes-before? a b) and (comes-before? b c) then it must ;;; be that (comes-before? a c). ;;; The comes-before? procedure excludes equal values. If a does not ;;; come before b and b does not come before a, then a equals b. ;;; Simlarly, if a equals b, then neither comes before the other. ;;; Postconditions: ;;; If vector contains no element whose key matches key, match is -1. ;;; If vec contains an element whose key equals key, match is the ;;; index of one such value. That is, key is ;;; (get-key (vector-ref vec match)) (define binary-search (lambda (key vec get-key comes-before?) ; Search a portion of the vector from lower-bound to upper-bound (let search-portion ((lower-bound 0) (upper-bound (- (vector-length vec) 1))) (if (<= lower-bound upper-bound) (let* ((midpoint (quotient (+ lower-bound upper-bound) 2)) (middle-element (vector-ref vec midpoint)) (middle-key (get-key middle-element))) (cond ((comes-before? key middle-key) (search-portion lower-bound (- midpoint 1))) ((comes-before? middle-key key) (search-portion (+ midpoint 1) upper-bound)) (else midpoint))) -1))))
I usually create these pages
on the fly, which means that I rarely
proofread them and they may contain bad grammar and incorrect details.
It also means that I tend to update them regularly (see the history for
more details). Feel free to contact me with any suggestions for changes.
This document was generated by
Siteweaver on Fri May 7 09:44:39 2004.
The source to the document was last modified on Wed Feb 25 09:22:34 2004.
This document may be found at