Dynamic Programming is one way which can be used as an optimization over plain recursion. Wherever we see a recursive solution that has repeated calls for the same inputs, we can optimize it using Dynamic Programming. The idea is to simply store the results of subproblems so that we do not have to re-compute them when needed later. This simple optimization reduces time complexities from exponential to polynomial. In this article, a method to use dictionaries of python to implement dynamic programming has been discussed.
In order to understand the implementation of the dynamic programming in python, lets visualize it using the Fibonacci numbers problem.
In mathematical terms, the sequence of Fibonacci numbers is defined by the recurrence relation:
Fn = Fn-1 + Fn-2
with seed values:
F0 = 0 and F1 = 1
Examples:
Input: N = 9
Output: 34
Explanation:
9th number in the Fibonacci series is 34.
Input: N = 2
Output: 1
Explanation:
2nd number in the Fibonacci series is 1.
Below is the implementation of the naive approach:
Python3
def Fibonacci(n):
if n< 0 :
print ( "Incorrect input" )
elif n = = 0 :
return 0
elif n = = 1 :
return 1
else :
return Fibonacci(n - 1 ) + Fibonacci(n - 2 )
print (Fibonacci( 9 ))
|
Clearly, the above approach has exponential time complexity. In order to store the previously computed results, let us use the dictionary class of python.
Approach: The idea is to customize the __missing__ method of the dictionary class. This method is executed when the user tries to access a key which is not in the dictionary. We will use our own function definition to rewrite this method.
Below is the implementation of the above approach:
Python3
class Fibonacci( dict ):
def __missing__( self , n):
if n< = 1 :
self [n] = n
return n
val = self [n] = self [n - 1 ] + self [n - 2 ]
return val
if __name__ = = "__main__" :
Fib = Fibonacci()
N = Fib[ 9 ]
print (N)
|
The above method can also be implemented by using a decorator in python.
Decorator is a very powerful and useful tool in python since it allows programmers to modify the behavior of function or class. Decorators allow us to wrap another function in order to extend the behavior of the wrapped function, without permanently modifying it. Here, memoization is used to implement a decorator.
Below is the implementation of the above approach:
Python3
from inspect import signature
class memoize( dict ):
def __init__( self , func):
self .func = func
self .signature = signature(func)
def __missing__( self , key):
(arg, kwarg) = key
self [key] = val = self .func( * arg,
* * dict (kwarg))
return val
def __call__( self , * arg, * * kwarg):
key = self .signature.bind( * arg,
* * kwarg)
return self [key.args,
frozenset (key.kwargs.items())]
@memoize
def Fibonacci(n):
if n< 0 :
print ( "Incorrect input" )
elif n = = 0 :
return 0
elif n = = 1 :
return 1
else :
return Fibonacci(n - 1 ) + Fibonacci(n - 2 )
if __name__ = = "__main__" :
print (Fibonacci( 9 ))
|
Feeling lost in the world of random DSA topics, wasting time without progress? It's time for a change! Join our DSA course, where we'll guide you on an exciting journey to master DSA efficiently and on schedule.
Ready to dive in? Explore our Free Demo Content and join our DSA course, trusted by over 100,000 geeks!
Last Updated :
09 Jul, 2021
Like Article
Save Article