Uploaded by Harry Jain

Introduction to Recursion

advertisement
Introduction to Recursion
Harry Jain
March 2020
Contents
1 Introduction
1.1 Recursion Basics . . . .
1.2 Aspects of Recursion .
1.3 Benefits of Recursion .
1.4 Downsides of Recursion
.
.
.
.
1
1
1
1
1
2 Recursive Problems
2.1 The Fibonacci Sequence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
2
3 Divide and Conquer Algorithms
3.1 Merge Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
CS50 Notes: Web Track
1
Harry Jain
Introduction
Quote 1.1: Computer Science Humor
“To understand recursion, you must understand recursion.”
While recursion is one of the most important and most common problem solving techniques in
computer science, it is unfortunately one of the most difficult to understand. Correspondingly, the
above quote may seem nonsensical to a recursion novice, but is really quite funny to an experienced
“recurser” (or at least I think so). However, after grasping a few basic concepts and doing a bit of
practice, recursion becomes a powerful and indispensable tool.
1.1
Recursion Basics
In short, recursion is the idea of breaking up a large problem into smaller problems of the same type.
Once reduced to a certain size, many problems become trivial, e.g. sorting a list when there are
only two (or even one) item(s) in it. Then, by combining the solutions to these sub-problems, we
are able to solve our initial problem more clearly and concisely.
1.2
Aspects of Recursion
In terms of code, there are two main aspects of a typical recursive solution:
• Recursive function: this is a function that solves the problem by breaking it into smaller
sub-problems and recursively calling itself to solve these sub-problems
• Base case(s): the recursive function must contain at least one base case that determines
when the recursion stops (otherwise it would get stuck in an infinite loop)
1.3
Benefits of Recursion
While a bit opaque at first, the many benefits of recursion have made it a long-lasting and prevalent
problem-solving technique. For example, recursion can
• Reduce time complexity (as seen in Merge Sort below)
• Make code easier to write and understand
• Naturally fit many common problems in computer science and graph theory due to its inherent
connection to trees
1.4
Downsides of Recursion
While recursion is a widely-used and very powerful technique, it does have several downsides, including
• Recursive “stack” uses more memory than other techniques
• Recursion can be slow for some problems or if it is implemented incorrectly
Page 1
Harry Jain
CS50 Notes: Web Track
2
Recursive Problems
Now, let’s consider what sorts of problems we can solve by recursion. Mirroring the basic aspects
of recursion, these problems need to have the following attributes:
• Able to be broken down into identical sub-problems: this condition guarantees that the
basic form of recursion is viable
• Easy to solve smaller sub-problems: this condition guarantees the existence of a base case,
i.e. the fact that we can easily sort lists of length 1 or 2
2.1
The Fibonacci Sequence
Now, let’s consider perhaps the most famous recursive problem, the Fibonacci sequence. Doing a
bit of arthimetic review, consider the following geometric series:
a1 = 2, a2 = 4, a3 = 8, . . .
We can define this series one of two ways:
1. Explicit: in this case, we can find any term an using a closed-form expression, i.e.
an = 2n
2. Recursive: in this case, we define each term based on the previous terms, i.e.
an = 2 × an−1
While many sequences like this can be written in both explicit and recursive forms, some can only
be defined recursively. One such example is the Fibonacci sequence, which is defined as
f0 = 0, f1 = 1, f2 = 1, f3 = 2, f4 = 3, f5 = 5, f6 = 8, . . .
(fn = fn−1 + fn−2 )
There is no simple explicit formula for the Fibonacci sequence, so it must be defined by the recursive
expression in parentheses above, i.e. the sum of the previous two numbers of the sequence. Outlining
the aforementioned aspects of recursion, we have
• Recursive function: fn = fn−1 + fn−2
• Base case(s): if n = 0, then fn = 0 and if n = 1, fn = 1
This structure easily translates into code, which is outlined in the Python example below.
1
2
3
4
5
6
7
8
9
10
11
#
Define the recursive fib function
def fib(n):
#
Base case of 0th item: return 0
if n == 0:
return 0
#
Base case of 1st item: return 1
elif n == 1:
return 1
#
Recursive step: add the previous two Fibonacci numbers
else:
return fib(n - 1) + fib(n - 2)
12
13
14
15
#
Print out the first 10 Fibonacci numbers
for i in range(10):
print(fib(i), end = " ")
16
17
print()
Page 2
Harry Jain
CS50 Notes: Web Track
3
Divide and Conquer Algorithms
Formalizing our previous discussion of recursive problems, we will take a look at a common class of
algorithms, called “Divide and Conquer.” These algorithms apply to the same kind of problems as
described above, i.e. those that can be broken down into smaller problems of the same type. In
terms of structure, these algorithms have three steps (from which they took their name):
• Divide: Break the problem into two or more problems of the same kind
• And: Solve each problem either recursively or using the base case
• Conquer: Combine the results of the sub-problems to get the solution to the original problem
Even with our previous recursive examples, this may seem a bit abstract. Thus, we will look at a
common example in Merge Sort, one of the more efficient sorting algorithms.
3.1
Merge Sort
To give a concrete example, let’s consider implementing Merge Sort, which sorts a list of length n
using the following recursive Divide and Conquer form
• Divide: Split the list into two sub-lists of size
n
2
• And: Sort each sub-list either recursively or using the base case (for a list of length 1, return
that element)
• Conquer: Merge the groups together by progressively adding the smallest (or largest) item
from either sub-list to the main list
Once again, while requiring a slightly more complex implementation, this structure fits rather neatly
into code, as outlined in the C program below.
1
2
#include <stdio.h>
#include <string.h>
3
4
5
void mergesort(int nums[], int l, int r);
void merge(int nums[], int l, int m, int r);
6
7
8
9
10
int main()
{
int nums[] = {1023, 37, 1, 2, 37, 23, 5, 2, 4, 2, 13, 522, 92, 23};
int num_count = sizeof(nums) / sizeof(nums[0]);
11
mergesort(nums, 0, num_count - 1);
12
13
for (int i = 0; i < num_count; i++)
{
printf("%i ", nums[i]);
}
14
15
16
17
18
printf("\n");
return 0;
19
20
21
}
22
23
24
//
//
Sort the items in nums at indicies between l and r,
inclusive
Page 3
CS50 Notes: Web Track
25
26
27
28
29
30
31
Harry Jain
void mergesort(int nums[], int l, int r)
{
//
Base case when there is only one element
if (l < r)
{
//
Calculate middle element
int m = (l + r) / 2;
32
//
Recursively call mergesort on the left and right half of the list
mergesort(nums, l, m);
mergesort(nums, m + 1, r);
33
34
35
36
//
Merge the sorted left and right lists
merge(nums, l, m, r);
37
38
}
39
40
}
41
42
43
44
45
46
47
48
//
Merge two sorted portions of nums: from l to m, inclusive, and from m + 1 to r,
//inclusive
void merge(int nums[], int l, int m, int r)
{
//
Calculate lengths of left and right subsets to merge
int llen = m - l + 1;
int rlen = r - m;
49
50
51
52
//
Declare the left and right subsets
int lefts[llen];
int rights[rlen];
53
54
55
56
57
58
59
60
61
62
//
Initialize the values of the left and right subsets
for (int i = 0; i < llen; i++)
{
lefts[i] = nums[i + l];
}
for (int j = 0; j < rlen; j++)
{
rights[j] = nums[j + m + 1];
}
63
64
65
66
//
Declare the number of elements used from each subset and set it equal to 0
int lindex = 0;
int rindex = 0;
67
68
69
70
71
72
73
74
75
76
//
Loop through each index of nums within our range
for (int k = l; k <= r; k++)
{
//
If there are unmerged items in both subsets, choose the larger one
if (lindex < llen && rindex < rlen)
{
if (lefts[lindex] >= rights[rindex])
{
nums[k] = lefts[lindex];
Page 4
CS50 Notes: Web Track
lindex++;
}
else
{
nums[k] = rights[rindex];
rindex++;
}
77
78
79
80
81
82
83
}
//
If the right subset has been exhausted and not the left,
//
place the rest of the left ones
else if (lindex < llen)
{
nums[k] = lefts[lindex];
lindex++;
}
84
85
86
87
88
89
90
91
}
92
93
Harry Jain
}
Page 5
Download