This preview shows pages 1–3. Sign up to view the full content.
CS 3110 Lecture 23
Memoization
Even when programming in a functional style,
O
(1) mutable map abstractions like arrays and hash tables can
be extremely useful. One important use of hash tables is for
memoization
, in which a previously computed
result is stored in the table and retrieved later. Memoization is a powerful technique for building efficient
algorithms, especially in a functional language.
For example, consider the problem of computing the nth Fibonacci number, defined as
f
(
n
) =
f
(
n
−1) +
f
(
n
−2).
We can translate this directly into code:
let f(n) = if n<2 then 1 else f(n1) + f(n2)
Unfortunately, this code takes exponential time: Θ(φ
n
), where φ is the golden ratio, (1 + √5)/2. We can easily
verify these asymptotic bounds by using the substitution method. Using the recurrence
T
(
n
) =
T
(
n
−1) +
T
(
n
−2) + 1, we show by induction that
T
(
n
)≤φ
n
−1:
T
(
n
) =
T
(
n
−1) +
T
(
n
−2) + 1
≤
k
φ
n
−1
− 1 +
k
φ
n
−2
−1 + 1
≤
k
φ
n
−1
+
k
φ
n
−2
− 1
But φ has the property that φ
2
= φ + 1, so:
k
φ
n
−1
+
k
φ
n
−2
=
k
φ
n
−2
(1 + φ)
=
k
φ
n
−2
φ
2
=
k
φ
n
Therefore
T
(
n
) ≤
k
φ
n
− 1 and
T
is
O
(φ
n
). The Ω direction is shown similarly.
The key observation is that the recursive implementation is inefficient because it recomputes the same
Fibonacci numbers over and over again. If we record Fibonacci numbers as they are computed, we can avoid
this redundant work. The idea is that whenever we compute
f(n)
, we store it in a table indexed by
n
. In this
case the indexing keys are integers, so we can use implement this table using an array:
let fibm(n) =
let memo: int option array = Array.create (n+1) None in
let rec f_mem(n) =
match memo.(n) with
Some result > result
(* computed already! *)
 None >
let result = if n<2 then 1 else f_mem(n1) + f_mem(n2) in
memo.(n) < (Some result);
(* record in table *)
result
in
f_mem(n)
The function
f_mem
defined inside
fibm
contains the original recursive algorithm, except before doing that
calculation it first checks if the result has already been computed and stored in the table in which case it
simply returns the result. How do we analyze the running time of this function? The time spent in a single
call to
f_mem
is
O(1)
if we exclude the time spent in any recursive calls that it happens to make. Now we
This preview has intentionally blurred sections. Sign up to view the full version.
View Full Documentlook for a way to bound the total number of recursive calls by finding some measure of the progress that is
being made.
A good choice of progress measure, not only here but also for many uses of memoization, is the number of
nonempty entries in the table (i.e. entries that contain
Some
integer value rather than
None
). Each time
f_mem
makes the two recursive calls it also increases the number of nonempty entries by one (filling in a
formerly empty entry in the table with a new value). Since the table has only
n
entries, there can thus only be
a total of
O(n)
calls to
f_mem
, for a total running time of
O(n)
(because we established above that each call
This is the end of the preview. Sign up
to
access the rest of the document.
 '07
 GIAMBATTISTA,A

Click to edit the document details