@RecursionVisualizer()
def fibonacci(n):
if n <= 2:
return 1
return fibonacci(n-1) + fibonacci(n-2)
recursion-visualizer
Stop drawing recursion trees by hand. RecursionVisualizer
creates beautiful, interactive visualizations with a single line of code.
Visualize computing the n-th fibonacci number like this:
5) fibonacci(
(None, 5)
Install
pip install recursion_visualizer
or
conda install -c conda-forge recursion_visualizer
How to Use
Simply add the RecursionVisualizer
decorator to your recursive function and get a beautiful, interactive animation!
Toggle the DP button to visualize which function calls are evaluated with and without dynamic programming (DP).
Examples
Fibonacci
Visualize computing the n-th fibonacci number like this:
@RecursionVisualizer()
def fibonacci(n):
if n <= 2:
return 1
return fibonacci(n-1) + fibonacci(n-2)
5) fibonacci(
(None, 5)
There are several things to note:
- Each node represents a single call to
fibonacci
- If
fibonacci(i)
callsfibonacci(i-1)
andfibonacci(i-2)
, then the nodei
will have childreni-1
andi-2
- The tree is rooted at
5
because we initially called the functionfibonacci(5)
1
and2
are the the leaves of this tree because the base cases offibonacci
is wheni=1
ori=2
- The animation illustrates the order in which the computer evaluates all of the
fibonacci
calls - Toggle the
DP
button to see how using dynamic programming (DP) changes which function calls are evaluated
0-1 Knapsack
Visualzie the 0-1 knapsack problem like this:
@RecursionVisualizer(display_args=[0])
def knapsack(capacity, weights, values, i, edge_label=''):
# create edge labels
= 'skip W={}, V={}'.format(weights[i-1], values[i-1])
label_1 = 'skip W={}, V={}'.format(weights[i-1], values[i-1])
label_2 = 'take W={}, V={}'.format(weights[i-1], values[i-1])
label_3
# base case
if i == 0 or capacity == 0:
return 0
# if the weight of the current item is more than the capacity
if weights[i-1] > capacity:
return knapsack(capacity, weights, values, i-1, edge_label=label_1)
# return the maximum of two cases: including the ith-item or not including it
return max(knapsack(capacity, weights, values, i-1, edge_label=label_2),
-1] + knapsack(capacity-weights[i-1], weights, values, i-1, edge_label=label_3)) values[i
= [10, 20]
weights = [60, 100]
values = 50
capacity
len(weights)) knapsack(capacity, weights, values,
(None, 160)
There are several things to note:
- Each node represents a single call to the
knapsack
function - The
display_args=[0]
parameter in@RecursionVisualizer
means that even thoughknapsack
takes in four arguments, we will only display the0
th argument in each node - Each node displays the the capacity, how much more weight you can add to your knapsack (this is the
0
th argument toknapsack
) - This tree has a branching factor of two because every level represents either taking or not taking the
i
th item
Edit Distance
Visualize computing the edit distance like this:
@RecursionVisualizer(display_args=[0, 1])
def edit_distance(m, n, str1, str2, edge_label=''):
# edge labels
= 's1={}, s2={}'.format(str1[:m], str2[:n])
replace_label = 's1={}, s2={}'.format(str1[:m+1], str2[:n])
insert_label = 's1={}, s2={}'.format(str1[:m], str2[:n+1])
remove_label
# base case
if m == 0 or n == 0:
return max(n, m)
# if the last characters are the same: compute distance for the remaining strings
if str1[m-1] == str2[n-1]:
return edit_distance(m-1, n-1, str1, str2, edge_label=replace_label)
# if last characters are not the same: insert, remove, and replace the last character, and return the minimum
return 1 + min(edit_distance(m, n-1, str1, str2, edge_label=insert_label), # insert
-1, n, str1, str2, edge_label=remove_label), # remove
edit_distance(m-1, n-1, str1, str2, edge_label=replace_label) # replace
edit_distance(m )
= "it", "hi"
str1, str2 len(str1), len(str2), str1, str2) edit_distance(
(None, 2)
Mergesort
Visualize the mergesort algorithm like this:
def mergesort_wrapper(nums):
def merge(lo, mid, hi):
"helper function for mergesort"
= nums[lo:mid+1] + [float('inf')], nums[mid+1:hi+1] + [float('inf')]
L, R = 0, 0
i, j for k in range(lo, hi+1):
if L[i] <= R[j]:
= L[i]
nums[k] += 1
i else:
= R[j]
nums[k] += 1
j
@RecursionVisualizer()
def mergesort(lo, hi, edge_label=''):
if lo < hi:
= lo + (hi-lo) // 2
mid ='nums={}'.format(nums[lo:mid+1]))
mergesort(lo, mid, edge_label+1, hi, edge_label='nums={}'.format(nums[mid+1:hi+1]))
mergesort(mid
merge(lo, mid, hi)return nums[lo:hi+1]
0, len(nums)-1)
mergesort(return nums
= [3, 1, 9, 4]
nums mergesort_wrapper(nums)
[1, 3, 4, 9]
Features
For all animations:
- Each node represents a single recursive function call
- The animation illustrates the order in which the computer evaluates each of these function calls
- Toggle the
DP
button to see how using dynamic programming (DP) changes which function calls are evaluated
Extra features:
- Hovering the cursor over a node displays additional information
- The nodes have different colors:
- A node is unvisited if it is white
- We are visiting a node if it is medium blue
- A node is visited if it is dark blue
- At any given time, the path of medium blue nodes illustrates the current functions in the call stack, ie the functions that are currently being executed
- The leaf nodes represent the base case
Limitations
RecursionVisualizer
is intended for educational purposes only. It is not intended for real world applications or commerical use.
To create an animation of a recursive function, RecursionVisualizer
must run the brute force version of the recursive function with no dynamic programming. This means that RecursionVisualizer
will often have an exponential runtime. For this reason, we recommend using RecursionVisualizer
on inputs no larger than n=10
. (n
may be the length of a string/list or the number of vertices/edges in a graph.)
Contributions
All contributions are welcome. Simply create a pull request to begin contributing.
Note: RecursionVisualizer
is made with nbdev
, a tool to create software with notebooks. For more information on nbdev go to their homepage.
License
MIT