| Overall Reading | |
|---|---|
| Brookshear: | Review Ch. 4.1-4.2 |
| Read pp. 184-190, Ch. 4.5, parts of pp. 206-211, 248-251, 333. |
Outline:
How do we solve the subproblems? Use the same algorithm recursively (until you get down to some sufficiently small base case).
Example: When parsing the arithmetic expression
((Y+4)*8/(5-(X+4)))We can imagine that part of the algorithm determined that the division in this example, was the top-level operator for the expression. After making this determination, the rest of the parsing was probably carried out by repeating the same algorithm on the two separate expressions:
(Y+4)*8 (5-(X+4))
Though the syntax may differ from language to language, the three basic elements of repetitive control include:
Common styles for specifying such repetitions includes:
Select the first entry in the list as the test entry.
while ( (target value > test entry) AND
(there remain entries to be considered) )
do (Select the next entry in the list as the test entry)
if (target value = test entry)
then (Declare the search a success.)
else (Declare the search a failure.)
The above algorithm works in general. However it fails to be
well-defined in the special case that the original list is empty.
We could fix this oversight, and rewrite our algorithm in a more
general form as a procedure with two parameters:
procedure Search (List, TargetValue)
if (List empty)
then
(Declare search a failure)
else
[Select the first entry in List as the test entry;
while ((TargetValue > test entry) AND
(there remain entries to be considered))
do (Select the next entry in List as the test entry.)
if (TargetValue = test entry)
then (Declare search a success.)
else (Declare search a failure.)
]
Let's revisit the idea of searching for a target in a sorted list. Please turn to page 198 of Brookshear.
(Did you use a sequential search to find page 198?)
When searching a sorted list, we can take great advantage of the order. Specifically, if we compare the target to an item near the middle of the list we can reduce the original problem to one of two subproblems: either searching the first half of the list or searching the second half of the list. Note, that we will never have to search both halves. In fact, if we find the target at the middle, we can stop immediately.
This intuition can be used to define a recursive algorithm for searching, called binary search. Not only will I use this idea to break the original problem into subproblems, but I will use the idea recursively to solve any necessary subproblem.
procedure Search (List, TargetValue)
if (List empty)
then
(Declare the search a failure.)
else
Select the "middle" entry in List as the test entry;
Execute one of the following blocks of instructions
depending on whether TargetValue is equal to, less
than or grater than the test entry
Case 1: TargetValue = test entry
(Declare this search a success.)
Case 2: TargetValue < test entry
[
Apply the procedure Search to see whether TargetValue
is in the portion of List preceding the test entry
if (that search is successful)
then (Declare this search a success.)
else (Declare this search a failure.)
]
Case 3: TargetValue > test entry
[
Apply the procedure Search to see whether TargetValue
is in the portion of List following the test entry
if (that search is successful)
then (Declare this search a success.)
else (Declare this search a failure.)
]
Here is a Binary Search Demonstration which you can run to see some examples.
Specifically, if we are performing a sequential search on a list with n items, you can expect that on average you will have to examine half of the items. In the worst case, you may have to examine each item.
How efficient is binary search?
Every time we consider a test entry, we either find the target, or else we effectively divide the size of the list by a factor of two. For small lists, this may not be such an improvement, but for large lists this improvement becomes dramatic. When using binary search,
This is briefly touched on in pp. 248-251 of Ch. 5.3. An even better picture is given, in a different context, by Figure 7.9 on p. 333 of [Br].
Informally, I like to consider making a procedure call analogous to having an assistant do some of my work while I sit and relax. Specifically, the process is to
For Recursion, we note that this same mechanism can be used. That is, there is nothing stopping us from defining a procedure which calls itself to handle a subtask.
The only key to doing this is to make sure that the recursion is defined in a way so that you will be sure that it terminates properly (just as we were concerned with terminating the repetition of loops).
Termination generally takes place by defining a "base case" which can be solved without the need of recursion.