Bilgisayar Mühendisliği Bölümü
DATA STRUCTURES AND ALGORITHMS Lecture Notes 9 Recursion Spring 2008
GIT – Computer Engineering Department
Chapter Outline Thinking recursively Tracing execution of a recursive method Writing recursive algorithms – Methods for searching arrays
Solving the Towers of Hanoi problem with recursion Processing 2-D images with recursion Backtracking to solve search problems, as in mazes Recursion vs. Iteration
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
2
Recursive Thinking Recursion is: – a problem-solving approach, that can ... – generate simple solutions to ... – certain kinds of problems that ... – would be difficult to solve in other ways
Recursion splits a problem: – Into one or more simpler versions of itself
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
3
Recursive Thinking: An Example Strategy for processing nested dolls: 1. if there is only one doll 2. do what it needed for it else 3. do what is needed for the outer doll 4. Process the inner nest in the same way
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
4
Recursive Thinking: Another Example Strategy for searching a sorted array: 1. if the array is empty 2. return -1 as the search result (not present) 3. else if the middle element == target 4. return subscript of the middle element 5. else if target < middle element 6. recursively search elements before middle 7. else 8. recursively search elements after the middle
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
5
Recursive Thinking: The General Approach 1. if problem is “small enough” 2. solve it directly 3. else 4. break into one or more smaller subproblems 5. solve each subproblem recursively 6. combine results into solution to whole problem
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
6
Requirements for Recursive Solution At least one “small” case that you can solve directly A way of breaking a larger problem down into: – One or more smaller subproblems – Each of the same kind as the original
A way of combining subproblem results into an overall solution to the larger problem
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
7
Tracing a Recursive Method Overall result
length(“ace”) 3
2
1
return 1 + length(“ce”)
return 1 + length(“e”)
return 1 + length(“”) 0
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
8
Tracing a Recursive Method (2)
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
9
Recursive Definitions of Mathematical Formulas Mathematicians often use recursive definitions These lead very naturally to recursive algorithms Examples include: – Factorial – Powers – Greatest common divisor
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
10
Recursive Definitions: Factorial 0! = 1 n! = n x (n-1)!
If a recursive function never reaches its base case, a stack overflow error occurs GIT – Computer Engineering Department
Simulation - Lecture Notes 8
11
Recursive Definitions: Factorial Code int factorial (int n) { if (n == 0) // or: throw exc. if < 0 return 1; else return n * factorial(n-1); }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
12
Recursive Definitions: Power x0 = 1 xn = x × xn-1 double power(double x, int n) { if (n <= 0) // or: throw exc. if < 0 return 1; else return x * power(x, n-1); }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
13
Recursive Definitions: Greatest Common Divisor
Definition of gcd(m, n), for integers m > n > 0: gcd(m, n) = n, if n divides m evenly gcd(m, n) = gcd(n, m % n), otherwise int gcd (int m, int n) { if (m < n) return gcd(n, m); else if (m % n == 0) // could check n>0 return n; else return gcd(n, m % n); }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
14
Recursive Definitions: Fibonacci Series Definition of fibi, for integer i > 0: fib1 = 1 fib2 = 1 fibn = fibn-1 + fibn-2, for n > 2
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
15
Fibonacci Series Code int fib (int n) { if (n <= 2) return 1; else return fib(n-1) + fib(n-2); }
This is straightforward, but an inefficient recursion ...
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
16
Efficiency of Recursion: Inefficient Fibonacci
# calls apparently O(2n) – big!
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
17
Efficient Fibonacci Strategy: keep track of: – Current Fibonacci number – Previous Fibonacci number – # left to compute
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
18
Efficient Fibonacci: Code int fibonacci_start (int n) { return fibo(1, 0, n); } int fibo (int curr, int prev, int n) { if (n <= 1) return curr; else return fibo(curr+prev, curr, n-1); }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
19
Efficient Fibonacci: A Trace
Performance is O(n)
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
20
Recursive Array Search Can search an array using recursion Simplest way is linear search – Examine one element at a time – Start with the first element, end with the last
One base case for recursive search: empty array – Result is -1 (negative, not an index not found)
Another base case: current element matches target – Result is index of current element
Recursive case: search rest, without current element
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
21
Recursive Array Search: Linear Algorithm 1. if the array is empty 2. return -1 3. else if first element matches target 4. return index of first element 5. else 6. return result of searching rest of the array, excluding the first element
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
22
Linear Array Search Code template
int linear_search ( const std::vector& items, const Item_Type& target) { return linear_search(items, target, 0); } template int linear_search ( const std::vector& items, const Item_Type& target, size_t pos_first) { if (pos_first == items.size()) return -1; else if (target == items[pos_first]) return pos_first; else return linear_search(items, target, pos_first+1); }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
23
Array Search: Getting Better Performance Item not found: O(n) Item found: n/2 on average, still O(n) How can we perhaps do better than this? – What if the array is sorted? – Can compare with middle item – Get two subproblems of size ≤ n/2
What performance would this have? – Divide by 2 at each recursion O(log n) – Much better! GIT – Computer Engineering Department
Simulation - Lecture Notes 8
24
Binary Search Algorithm Works only on sorted arrays! Base cases: – Empty (sub)array – Current element matches the target
Check middle element with the target Consider only the array half where target can still lie
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
25
Binary Search Algorithm Steps 1. 2. 3. 4. 5. 6. 7. 8.
if array is empty return -1 as result else if middle element matches return index of middle element as result else if target < middle element return result of searching lower portion of array else return result of searching upper portion of array
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
26
Binary Search Example
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
27
Binary Search Code template int binary_search(const std::vector& items, int first, int last, const Item_Type& target) { if (first > last) return -1; // Base case for unsuccessful search. else { int middle = (first + last) / 2; // Next probe index. if (target < items[middle]) return binary_search(items, first, middle - 1, target); else if (items[middle] < target) return binary_search(items, middle + 1, last, target); else return middle; // Base case for successful search. } }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
28
Binary Search Code (2) template int binary_search(const std::vectoritems, const Item_Type& target) { return binary_search(items, 0, items.size() - 1, target); }
C++ Standard library function binary_search defined in does this.
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
29
Problem Solving with Recursion Towers of Hanoi Counting grid squares in a blob Backtracking, as in maze search
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
30
Towers of Hanoi: Description Goal: Move entire tower to another peg Rules: 1. You can move only the top disk from a peg. 2. You can only put a smaller on a larger disk (or on an empty peg)
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
31
Towers of Hanoi: Solution Strategy
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
32
Towers of Hanoi: Solution Strategy (2)
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
33
Towers of Hanoi: Program Specification Problem Inputs Number of disks (an integer) Letter of starting peg: L (left), M (middle), or R (right) Letter of destination peg (L, M, or R), but different from starting peg. Letter of temporary peg (L, M, or R), but different from starting peg and destination peg.
Problem Outputs A list of moves
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
34
Towers of Hanoi: Program Specification (2)
Function
Behavior
void show_moves(int n, char start_peg, char dest_peg, chat temp_peg)
Builds a string containing all moves for a game with n disks on start_peg that will be moved to dest_peg, using temp_peg for temporary storage of disks being moved.
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
35
Towers of Hanoi: Recursion Structure move(n, src, dst, tmp) = if n == 1: move disk 1 from src to dst otherwise: move(n-1, src, tmp, dst) move disk n from src to dst move(n-1, tmp, dst, src)
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
36
Towers of Hanoi: Code void show_moves(int n, char start_peg, char dest_peg, char temp_peg) { if (n == 1) { cout << "Move disk 1 from peg " << start_peg << " to peg " << dest_peg << "\n"; } else { // Recursive step show_moves(n - 1, start_peg, temp_peg, dest_peg); cout << "Move disk " << n << " from peg " << start_peg << " to peg " << dest_peg << "\n"; show_moves(n - 1, temp_peg, dest_peg, start_peg); } }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
37
Towers of Hanoi: Performance Analysis How big will the output be for a tower of size n? We’ll just count lines; call this L(n). For n = 1, one line: L(1) = 1 For n > 1, one line plus twice L for next smaller size: L(n+1) = 2 x L(n) + 1
Solving this gives L(n) = 2n – 1 = O(2n) So, don’t try this for very large n.
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
38
Counting Cells in a Blob Desire: Process an image presented as a two-dimensional array of color values Information in the image may come from – X-Ray – MRI – Satellite imagery – Etc.
Goal: Determine size of any area considered abnormal because of its color values GIT – Computer Engineering Department
Simulation - Lecture Notes 8
39
Counting Cells in a Blob (2) A blob is a collection of contiguous cells that are abnormal By contiguous we mean cells that are adjacent, horizontally, vertically, or diagonally
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
40
Counting Cells in a Blob: Example
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
41
Counting Cells in a Blob: Recursive Algorithm Algorithm count_cells(x, y): if (x, y) outside grid return 0 else if color at (x, y) normal return 0 else Set color at (x, y) to “Temporary” (normal) return 1 + sum of count_cells on neighbors
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
42
Count Cells Code int count_cells(color grid[ROW_SIZE][COL_SIZE], int r, int c) { if (r < 0 || r >= ROW_SIZE || c < { return 0; } else if (grid[r][c] != ABNORMAL) { return 0; } else { grid[r][c] = TEMPORARY; return 1 + count_cells(grid, r - 1, c + count_cells(grid, r - 1, c + count_cells(grid, r + 1, c + count_cells(grid, r + 1, c }
0 || c >= COL_SIZE)
+ + -
1) 1) 1) 1)
+ + + +
count_cells(grid, count_cells(grid, count_cells(grid, count_cells(grid,
r - 1, r, c + r + 1, r, c -
c) 1) c) 1);
}
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
43
Backtracking Backtracking: systematic trial and error search for solution to a problem – Example: Finding a path through a maze
In walking through a maze, probably walk a path as far as you can go – Eventually, reach destination or dead end – If dead end, must retrace your steps – Loops: stop when reach place you’ve been before
Backtracking systematically tries alternative paths and eliminates them if they don’t work
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
44
Backtracking (2) If you never try exact same path more than once, and You try all possibilities, You will eventually find a solution path if one exists Problems solved by backtracking: a set of choices Recursion implements backtracking straightforwardly – Activation frame remembers choice made at that decision point
A chess playing program likely involves backtracking
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
45
Maze Solving Algorithm: findPath(x, y) 1. 2. 3. 4. 5. 6. 7. 8. 9.
if (x,y) outside grid, return false if (x,y) barrier or visited, return false if (x,y) is maze exit, color PATH and return true else: set (x,y) color to PATH (“optimistically”) for each neighbor of (x,y) if findPath(neighbor), return true set (x,y) color to TEMPORARY (“visited”) return false
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
46
Maze Solving Code bool findMazePath(color grid[ROW_SIZE][COL_SIZE], int r, int c) { if (r < 0 || c < 0 || r >= ROW_SIZE || c >= COL_SIZE) return false; // Cell is out of bounds. else if (grid[r][c] != BACKGROUND) return false; // Cell is on barrier or dead end. else if (r == ROW_SIZE - 1 && c == COL_SIZE - 1) { grid[r][c] = PATH; // Cell is on path return true; // and is maze exit. } else . . . }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
47
Maze Solving Code (2) {
// Recursive case. // Attempt to find a path from each neighbor. // Tentatively mark cell as on path. grid[r][c] = PATH; if (findMazePath(grid, r - 1, c) || findMazePath(grid, r + 1, c) || findMazePath(grid, r, c - 1) || findMazePath(grid, r, c + 1 ) ) { return true; } else { grid[r][c] = TEMPORARY; // Dead end. return false; }
}
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
48
Recursion Versus Iteration Recursion and iteration are similar Iteration: – Loop repetition test determines whether to exit
Recursion: – Condition tests for a base case
Recursive method often slower than iterative; why? – Overhead for loop repetition smaller than overhead for call and return
If easier to develop algorithm using recursion, – Then code it as a recursive method (simpler than iterative) • easier to write, read, and debug
– Software engineering benefit probably outweighs ... • reduction in efficiency
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
49
Recursion Versus Iteration Recursive subroutines are implemented using stacks – Programmer who use recursion does not have to think about stacks – This is done automatically
Remember the string size example:
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
50
Recursion Iteration It is possible for a programmer to implement a recursive algorithm iteratively “by hand” – using a stack structure – a non-recursive routine might be faster than a recursive routine
Advantages – – – – –
have advantages of iterative solutions no additional function calls protect from stack overflow run faster use less memory
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
51
Recursion Iteration When recursion involves single call that is at the end ... It is called tail recursion and it easy to make iterative:
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
52
Example: Print an array of integers void Print(int X[],int N) { if (N == 0) cout << X[0] << "\n"; //stopping case else { cout << X[N] << "\n"; PrintBack(X, N - 1); //recursive step } }
void Print(int X[],int N) { while(N > 0){ cout << X[N] << "\n"; N = N - 1; } cout << X[0] << "\n"; }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
53
Example: String length int size(string str) { if (str == "") return 0; else return 1 + size(str.substr(1)); } int iterSize(string str) { int len = 0; while (str == ""){ len=len+1 str = str.substr(1); } } GIT – Computer Engineering Department
Simulation - Lecture Notes 8
54
Example: Factorial int factorial (int n) { if (n == 0) // or: throw exc. if < 0 return 1; else return n * factorial(n-1); } int iterFact (int n) { int result = 1; for (int k = 1; k <= n; k++) { result = result * k; } return result; }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
55
Example: Binary Search template int binary_search(const std::vector& items, int first, int last, const Item_Type& target) { int middle; while (first <= last){ middle = (first + last) / 2; if (target < items[middle]) middle = middle - 1; else if (items[middle] < target) first = middle + 1; else return middle; } return -1; }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
56
Example: Binary Search template int binary_search(const std::vector& items, int first, int last, const Item_Type& target) { if (first > last) return -1; // Base case for unsuccessful search. else { int middle = (first + last) / 2; // Next probe index. if (target < items[middle]) return binary_search(items, first, middle - 1, target); else if (items[middle] < target) return binary_search(items, middle + 1, last, target); else return middle; // Base case for successful search. } }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
57
Recursion Iteration: using a stack If not tail recursion but recursion is at the end:
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
58
Example: String length int size(string str) { { ParamStack.Push(str); while (!ParamStack.IsEmpty()){ ParamStack.Pop(str); if (str == "") len =0; else { len=len+1; ParamStack.Push(str.substr(1)); } } int size(string str) { } if (str == "") return 0; else return 1 + size(str.substr(1)); } GIT – Computer Engineering Department
Simulation - Lecture Notes 8
59
Example: Print an array of integers void Print(int X[],int N) { ParamStack.Push(N); while (!ParamStack.IsEmpty()){ ParamStack.Pop(N); if(N == 0) cout << X[0] << "\n"; else { cout << X[N] << "\n"; ParamStack.Push(N - 1); } void Print(int X[],int N) { } if (N == 0) } cout << X[0] << "\n"; else { cout << X[N] << "\n"; PrintBack(X, N - 1); }
} GIT – Computer Engineering Department
Simulation - Lecture Notes 8
60
Towers of Hanoi (Recursive)
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
61
Towers of Hanoi (Iterative)
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
62
Other Examples Counting Cells in a Blob – Can we use the same method?
What about maze solving? – Recursive calls are not at the end?
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
63
Example: Print an array elements in reverse printback(a,n) if(n==0) return else printback(a+1,n-1) print(*a) printback(a,n) Stack s; s.push(a,n,0) while s.notempty a,n,i=s.pop() if(n==0) continue if(i==0) s.push(a,n,i+1) s.push(a+1,n-1,0) else if(i==1) print(*a) else ERROR GIT – Computer Engineering Department
Simulation - Lecture Notes 8
64
Example: Counting nodes in a tree // Counts the number of nodes in a tree Recursive count: int count(TreeNode *t) { int a=0; if t==NULL return 0; else{ a+=count(t->left); a+=count(t->right); a=a+1; return a; } }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
65
Example: Counting nodes in a tree Iterative count: s.push(t,0,0,NULL) while stack not empty { t,a,i,r=s.pop() if(t==NULL){ t’,a’,i’,r’=s.pop() r’=0 s.push(t’,a’,i’,r’) continue }
else{ if(i==0){ s.push(t,a,i+1,r) s.push(t->left,0,0,NULL) } else if(i==1){ a=a+r s.push(t,a,i+1,NULL) s.push(t->right,0,0,NULL) } else if(i==2){ a=a+r a=a+1 t’,a’,i’,r’=s.pop() r’=a s.push(t’,a’,i’,r’) } } }
GIT – Computer Engineering Department
Simulation - Lecture Notes 8
66