C++ Memoization understanding

2020-02-15 07:21发布

问题:

I was trying to understand how memoization works in C++, so I looked at an example of memoization used in Fib. sequence.

std::map<int, int> fibHash;

int memoized_fib (int n)
{
    std::map<int, int>::iterator fibIter = fibHash.find(n);
    if( fibIter != fibHash.end() ) return *fibIter;

    int fib_val;
    if( n <=1 )    fib_val = 1;
    else           fib_val = memoized_fib ( n-1 ) + memoized_fib ( n-2 );

    fibHash[ n ] = fib_val;
    return fib_val;
}

I was a little confused with how the fibHash[n] works. Does it just hold the individual values of each fib(#)? Also, the iterator traversess through the index to look for the correct value in the table and returns that? For example fib(6) = find fib(5) and fib(4), already stored and just add them?

回答1:

The code indeed save each fib_val into the fibHash map. The find method called on fibHash searches the map to see if the value was previously computed. If so, find returns an iterator on this value and the function returns it (return *fibIter).

fibHash[ n ] = fib_val; adds a new value in the map.



回答2:

What you are saying is essentially correct.

"Does it [fibHash] just hold the individual values of each fib(#)?"

Yes, exactly. The values are filled in as they are calculated (with fibHash[ n ] = fib_val;). Lower fib values are used to calculate the higher ones.

The fibHash map maps X to fib(X), plain and simple.

The advantage of doing it this way is that if you calculate fib(20) followed by fib(21) and fib(23), then perhaps fib(15), you only have to calculate the intermediate values once.

The cost for this speedup is the memory of storing the values in fibHash.



回答3:

Does it just hold the individual values of each fib(#)?

Yes.

Also, the iterator traversess through the index to look for the correct value in the table and returns that?

Yes.

For example fib(6) = find fib(5) and fib(4), already stored and just add them?

Depends. First fib(6) searches to see if fib(6) has been called before. If it has then the stored answer gets returned. If it has never been called then fib(5) and fib(4) are called. The interesting thing is that if fib(5) has to be calculated it calls fib(4) before fib(6) does*, and then when fib(6) also calls fib(4) the result is guaranteed to found in fibHash because fib(5) already called fib(4). This is what causes the collapse of fib(n) from exponential time into something more like linear.

The naive recursive implementation of Fibonacci boils down to adding 1 together many times.

fib(5) =
fib(4)                                     + fib(3) =
fib(3)                   + fib(2)          + fib(2)          + fib(1) =
fib(2)          + fib(1) + fib(1) + fib(0) + fib(1) + fib(0) + fib(1) =
fib(1) + fib(0) + fib(1) + fib(1) + fib(0) + fib(1) + fib(0) + fib(1) = 
1      + 1      + 1      + 1      + 1      + 1      + 1      + 1 =
8

In fact, to calculate fib(n) you end up doing fib(n)-1 additions. But if in the course of calculating fib(n) you save and use previously calculated Fibonacci numbers then you no longer have to perform so many additions. To calculate fib(n) this way only requires n additions:

fib(5) =
fib(4)                            + fib(3) =
fib(3)                   + fib(2) + fib(3) =
fib(2)          + fib(1) + fib(2) + fib(3) =
fib(1) + fib(0) + fib(1) + fib(2) + fib(3) = 
1      + 1      + 1      + 2      + 3 =
8

* Although the order isn't guaranteed. fib(6) may call fib(4) first, and then when fib(6) calls fib(5) fib(5) calls fib(4) which is now guaranteed to return a stored value.