240 likes | 428 Views
Memoization. Algorithms Baojian Hua huabj@mail.ustc.edu.cn April 21, 2008. What ’ s memoization?. A systematic method for “ memoizing ” the results of some computation or even arbitrary operations To save the intermediate results To log (debug) …
E N D
Memoization Algorithms Baojian Hua huabj@mail.ustc.edu.cn April 21, 2008
What’s memoization? • A systematic method for “memoizing” the results of some computation or even arbitrary operations • To save the intermediate results • To log (debug) • … • To ensure correctness (later in this course) • Ex: graph traversal (say DFS) • To reduce complexity • Topic of this slide
Motivating Example /* For an array of string “strs”, we apply some * computation “trans” on each string “s” */ String[] strs = {“aa”, “bb”, “cc”, “aa”, “aa”, …}; void foo(trans:->){ for (String s : strs){ v = trans(s); } }// however, if “trans” is slow, the function // “foo” will be VERY expensive
Motivating Example /* A very simple (but effective) hack is to * memoize the comp’ that have been finished */ String[] strs = {“aa”, “bb”, “cc”, “aa”, “aa”, …}; StringMap<String, X> map; void memoize(trans:->){ for (String s : strs){ if (lookup(map, s)!=null) then return lookup(map, s); else { v = trans(s); enter(map, s, v); } }
Memoizing Intermediate Computations /* Factorial numbers */ fac(int n){ if (n==0) return 1; else return n*fac(n-1); } // It’s crucial to notice that in computing // “fac(n)”, we computed // all values from “fac(0)” to “fac(n-1)”
Memoizing Intermediate Computations /* So, it’s easy to memoize all of them with an * auxiliary map, say an array “result” */ int[] result; fac(int n){ if (n==0) return 1; else{ int tmp = n * fac(n-1); result[n] = tmp; return tmp; } // It’s a save for computing: for (int i=0; i<n; i++) fac(n);
Recursion: Revisited • Divide Conquer • Divide: • Initial problems could be divided into several sub-problems which are NOT overlapping. • Conquer: • Solve these sub-problems individually • Combine • Find the final answer by combing sub-ones
Divide Conquer • Sort: • nearly all • Select: • Searching: • bst, red-black, …
Overlapping Recursions • Overlapping recursions: • with overlapping sub-problems • Ex: int fib (int n){ if (n==0) return 0; else if (n==1) return 1; else return fib (n-1) + fib (n-2); } // though simple, it’s rather slow (try demo …)
Complexity • The recurrence equation: T(n) = T(n-1) + T(n-2) The sotion is T(n) = \phi^n, where \phi = 1.618 (golden ratio) Exponential! • It’s elegant, but not usable in practice • The problems is that we are re-computing many sub-items many times…
Another Approach • Use an array arr[n] to store fib(n): arr[0] = 0; arr[1] = 1; for (int i=2; i<=n; i++){ arr[i] = arr[i-1] + arr[i-2]; } // which not only computes fib(n), but also fib(i)
Moral • The total running time is O(n) • We never re-compute any value • any value (array item) is computed and assigned just once (after the initialization) • This is one simplest form of bottom-up memoization
Memoization: top-down int[] arr; // with proper initialization, say -1 int fib (int n){ if (n==0) return 0; else if (n==1) return 1; else if(arr[n]>0) // fib(n) has been computed return arr[n]; else{ int tmp = fib(n-1) + fib(n-2); arr[n] = tmp; return tmp; } }
Memoization: top-down int[] arr; // with proper initialization, say -1 int fib (int n){ if (n==0) return 0; else if (n==1) return 1; else if(arr[n]>0) // fib(n) has been computed return arr[n]; else{ int tmp = fib(n-1) + fib(n-2); arr[n] = tmp; return tmp; } } // with a lazy flavor
Moral • This is the top-down memoization • It’s more attractive for: • it’s more natural for problem trans’ • the order of the computation take care of itself • answers for all of the sub-problems may NOT be computed at all
Table Representation • Above examples make use of a simple table representation • linear structure: int[] arr; • In principal, it’s mapping • map: X->Y • So, any search table rep’ strategies works • arrary-based, list-based, tree-based, hash, … • Next, we explore hash-table
Hash-table (Map)-based HashTable<Integer, Integer> ht = new …(); int fib (int n){ if (n==0) return 0; else if (n==1) return 1; else if(ht.lookup(n)) // fib(n) computed return ht.lookup(n); else{ int tmp = fib(n-1) + fib(n-2); ht.insert(n, tmp); return tmp; } }
Advantage • More natural for problem representation • Uniform: always a map • Don’t blurred by annoying details • Ex: array positions • Easy to extend to more complex situa’ • See next example
Multi-dimension Recursion f(m, n) = 0, if m>n f(m, n) = 1, if m=0 or m=n f(m, n) = f(m, n-1) + f(m-1, n-1) // f maps every integer tuple (m, n) to an // integer f(m, n)
Hash-table (Map)-based HashTable<Tuple<Integer, Integer>, Integer> ht = new …(); int f (int m, int n){ if (m>n) return 0; else if (m==0 || m==n) return 1; else if(ht.lookup(Tuple<m, n>)) return ht.lookup(Tuple<m, n>); else{ int tmp = f (m, n-1) + f (m-1, n-1); ht.insert(Tuple<m, n>, tmp); return tmp; } }