Presentation is loading. Please wait.

Presentation is loading. Please wait.

Recursion Chapter 5.

Similar presentations


Presentation on theme: "Recursion Chapter 5."— Presentation transcript:

1 Recursion Chapter 5

2 Objectives Discuss the system stack in more detail.
Introduce the tree representation of stack operations. Introduce recursion. Recurrence relations Base case Simplification step Designing recursive algorithms Solve the Towers of Hanoi problem. Recursive design Computational complexity Discuss when not to use recursion. Tail recursion Solve the 8 Queens problem. Solving mazes recursively.

3 Homework Overview Written (max 18 points) Programming (max 20 points)
5.1 E1 (a, c, e) (2 pts each) 5.1 E2 (a, c, e) (2 pts each) 5.2 E2 (d) (2 pts) 5.2 E4 (b) (3 pts) 5.3 E1 (3 pts) 5.3 E2 (4 pts) Programming (max 20 points) 5.1 P1 (10 pts) 5.2 E2 (a ,b, c) (5 pts each) 5.2 E4 (a) (8 pts) Group Project (38 points) 5.3 P3 (38 pts)

4 System Stack The computer keeps track of function calls (and all the associated local variables) using a stack. Suppose A, B, C and D are functions while M is the main function. Every time a function is called it is pushed onto the stack. Every time a function ends it is popped off the stack.

5 Tree Representation We can draw this same series of function calls using a tree. We traverse the tree from left to right.

6 Factorials The factorial function is defined Example:
We can also define the factorial in terms of itself using a recurrence relation.

7 Factorials We can write this definition in C++.
int fact(int n) { if (n==0) return 1; else Return n * fact(n-1); } Calculating a particular value is messy but straightforward. fact(4) = 4*fact(3) = 4*(3*fact(2)) = 4*(3*(2*fact(1))) = 4*(3*(2*(1*fact(0)))) = 4*(3*(2*(1*1))) = 4*(3*(2*1)) = 4*(3*2) = 4*6 = 24

8 Factorials The sequence of stack frames: The tree representation:

9 Recursive Process A recursive process has two parts:
A base case (or cases) that are processed directly without recursion. A general method that reduces all the other cases to one (or more) cases that are simpler (closer to the base case). This allows us to think of the big picture and leave the details to the compiler and system stack.

10 Towers of Hanoi The Towers of Hanoi is a puzzle with three posts some number of different sized disks. The disks are placed on a post in increasing order by size. You can only move one disks one at a time, never placing a larger disk on a smaller one. The goal is to move the entire stack from post 1 to post 3.

11 Towers of Hanoi Algorithm
Idea – we will do this recursively. We know how to move one disk so this will be our base case. We will divide the problem of moving n disks into the problem of moving the top n-1 disks and moving the bottom one. Moving n-1 disks is closer to moving 1 disk (the base case) than the problem of moving n disks. Don’t worry how we will move the n-1 disks. We will use recursion to solve that problem.

12 Towers of Hanoi Algorithm
To move the 7 disks from post 1 to post 3 we will divide the problem into 3 parts. Move the top 6 from post 1 to post 2. Move the bottom disk from post 1 to post 3. Move the top 6 from post 2 to post 3.

13 Towers of Hanoi Algorithm
Let’s create a function to implement this recursive idea. void move (int count, int start, int finish, int temp); count – the number of disks to move start – the post we are moving the disks from. finish – the post we want to move the disks to. temp – the third post which we can used as temporary storage.

14 Towers of Hanoi Algorithm
To move the 7 disks from post 1 to post 3 the code will look like this. move(6, 1, 2, 3); cout << “move disk 7 from post 1 to post 3” << endl; move(6, 2, 3, 1);

15 Towers of Hanoi Algorithm
Our recursive process is as follows: The base case is moving 0 disks, there is nothing to do. The general rule is that to move n disks from post start to post finish using post temp we do the following. Move n-1 disks from start to temp. Move the bottom disk from start to finish. Move n-1 disks from temp to finish.

16 Towers of Hanoi Code #include<iostream> using namespace std; const int disks = 8; // Keep this small for an actual program run void move(int count, int start, int finish, int temp); /* Post: The first count disks have been moved from post start to post finish. */ int main() /* Post: The simulation of the Towers of Hanoi has terminated. */ { move(disks, 1, 3, 2); } void move(int count, int start, int finish, int temp) /* Post: The first count disks have been moved from post start to post finish. */ if (count > 0) { move(count - 1, start, temp, finish); cout << "Move disk " << count << " from " << start << " to " << finish << "." << endl; move(count - 1, temp, finish, start);

17 Towers of Hanoi Recursion Tree
Think of the recursion tree: How many times will the function be called? The number of moves will be

18 Towers of Hanoi Complexity
Suppose there are 64 disks. How long will it take the computer to solve the problem? The function will be called times. There will be moves.

19 Towers of Hanoi Complexity
On my computer it takes seconds to do the problem with 8 disks. This is function calls. This means it would take seconds. This is years!

20 Homework – Section 5.1 (page 169)
Written E1 (a, c, e) (written on paper) (2 pts each) E2 (a, c, e) (written on paper) (2 pts each) Programming P1 ( code, include analysis of results on your computer, see footnote) (10 pts)

21 Designing Recursive Algorithms
The steps to designing a recursive algorithm are: Key step – find a way to describe the solution of the problem in terms of “simpler” versions of the same problem. Stopping rule – find a simple case (base case) that you can solve directly. Check for termination – make sure that when you repeatedly simplify the problem you will eventually reach the base case. Recursion that does not terminate is deadly! Outline – combine the key step and the stopping rule and find a test to determine which rule to use. Recursion tree – if you are concerned about how long the algorithm will take you can analyze the recursions tree.

22 Fibonacci Numbers The Fibonacci numbers can be calculated recursively.
This seems like a perfect situation for recursion. We have two base cases. We have a simplification rule. For any natural number n we will eventually get to the base cases.

23 Recursive Fibonacci Code
int fibonacci(int n) { if (n <= 0) return 0; else if (n == 1) return 1; else return fibonacci(n-1) + fibonacci(n-2); } This is simple enough, but is it a good way to solve the problem? Let’s look at the recursion tree and find out.

24 Fibonacci Recursion Tree
This is the recursion tree for fibonacci(6). There are 25 function calls. Notice that fibonacci(2) is calculated 5 different times. This doesn’t seem very efficient.

25 A Better Fibonacci Algorithm
It is must faster to start at the small Fibonacci numbers and work our way up, remembering the numbers as we go. f(0) = 0 f(1) = 1 f(2) = f(1) + f(0) = = 1 f(3) = f(2) + f(1) = = 2 f(4) = f(3) + f(2) = = 3 f(5) = f(4) + f(3) = = 5 f(6) = f(5) + f(4) = = 8

26 Improved Fibonacci Code
int fibonacci(int n) /* Pre: The parameter n is a nonnegative integer. Post: The function returns the nth Fibonacci number. */ { int last_but_one; // second previous Fibonacci number, F(n-2) int last_value; // previous Fibonacci number, F(n-1) int current; // current Fibonacci number, F(n) if (n <= 0) return 0; else if (n == 1) return 1; else { last_but_one = 0; last_value = 1; for (int i = 2; i <= n; i++){ current = last_but_one + last_value; last_but_one = last_value; last_value = current; } return current;

27 Reconsidering Recursion
Recursion is a powerful tool but it is easy to use in cases when it is not the best solution. Essentially, recursion just uses the system stack to keep track of functions calls. We could always write a program that maintains the same information without recursion. Any recursive program can be written iteratively (using a loop). Recursion also faces a performance penalty due to all the function calls. Recursion is best used when it would be difficult to maintain the information directly.

28 Tail Recursion Tail recursion – a function that:
Calls itself only once. Calls itself as the last thing it does. As an example consider this following recursive function to calculate factorials. int factorial (int n) { if(n == 0) return 1; else return n * factorial(n-1); }

29 When to Avoid Recursion
It is always relatively easy to turn a function that uses tail recursion into one that uses a loop. Any other recursive function that only calls itself one time can also be converted. It is usually easiest with tail recursion. Recursion may not be the best choice if the recursion tree has lots of duplication.

30 Homework – Section 5.2 (page 181)
Written E2 (d) (written on paper) (2 pts) E4 (b) (written on paper) (3 pts) Programming E2 (a, b, c)( code) (5 pts each) E4 (a)( code) (8 pts)

31 8 Queens Problem 8 Queens Problem – can you place 8 Queens on a chess board so that no two Queens have each other in check? Queens more in straight lines. Horizontally Vertically Diagonally The chess board is made up 8 rows and 8 columns of squares.

32 8 Queens Problem This is not a solution:
The Queens on the upper left and lower right share the same diagonal.

33 Solution Strategies Here are some ideas for solving the problem:
Place Queens at random and check the results. This will eventually produce solutions. We will never know if we have found all the solutions (if there are more than one). If we don’t find a solution it is hard to know when to quit. Look for a pattern (use mathematics). Best method, if we can do it. It may take a lot of thinking. Place Queens in a systematic way looking at all possible configurations. Lots of work – use a computer. It will find all the solutions.

34 8 Queens Algorithm solve_from(Queen_config) { if Queen_config has 8 queens print the result else for every open chessboard square p { add Queen on square p Remove queen from square p } Backtracking – we proceed (blindly) with the problem until we hit a dead-end. Then we backtrack until new options open up.

35 8 Queens Algorithm Since there are 8 Queens and 8 rows, there must be a Queen in each row. Let’s start with the first row and place a Queen. Then we can move to the second row, find an open square and place a second Queen. Continue with the remaining rows. If we reach a point where a row does not have any open squares then we will backtrack to find unexplored configurations.

36 8 Queens Data Storing the chess board turns out not to be the easiest approach. We would need functions to test which diagonals, and columns are open. It is easier to simply store arrays that represent the diagonals and the columns. For each row, we will store the column for the Queen in that row. We will also maintain an array that keeps track of which columns are free to add a queen. We could calculate this from the first array, but this is easier.

37 8 Queens Column Data Suppose we have placed 3 Queens.
They will be in rows 0, 1 and 2. queen_in_row col_free 2 4 F T

38 8 Queens Diagonal Data We will also keep track of which diagonals are free. 15 upward diagonals. 15 downward diagonals. The number of each type of diagonal is 2 * board_size – 1 We will maintain two arrays upward_free downward_free

39 8 Queens Diagonal Data The number of the upward diagonal is
row + column upward_free F T

40 row - column + board_size - 1
8 Queens Diagonal Data The number of the downward diagonal is row - column + board_size - 1 downward_free T F

41 Queens Class To implement our backtracking algorithm we will create a Queens class that will keep track of all the data. This class will have the following methods: Constructor – set up an empty game board. is_solved – determine if the current configuration solves the problem. ungaurded – determine if a given square is open. insert – add a queen to the board in a particular square. remove – remove a queen from the board in a particular square. print – print out the solution.

42 Queens Class const int max_board = 30; class Queens { public: Queens(int size); // Constructor /* Post: The Queens object is set up as an empty configuration on a chessboard with size squares in each row and column. */ bool is_solved() const; /* Post: Returns true if the current configuration contains board_size queens and returns false otherwise. */ void print() const; /* Post: Prints the current configuration. */ bool unguarded(int col) const; /* Post: Returns true or false according as the square in the first unoccupied row (row count) and column col is not guarded by any queen. */ void insert(int col); /* Pre: The square in the first unoccupied row (row count) and column col is not guarded by any queen. Post: The queen has been inserted into the square at row count and column col; count has been incremented by 1. */ void remove(int col); /* Pre: The square located at row count and column col is occupied by a queen. Post: The queen has been removed from the square at row count and column col; count has been decremented by 1. */

43 Queens Class int board_size; // dimension of board = maximum number of queens private: int count; // current number of queens = first unoccupied row bool col_free[max_board]; bool upward_free[2 * max_board - 1]; bool downward_free[2 * max_board - 1]; int queen_in_row[max_board]; // column number of queen in each row };

44 Queens Methods void Queens::insert(int col) /* Pre: The square in the first unoccupied row (row count) and column col is not guarded by any queen. Post: The queen has been inserted into the square at row count and column col; count has been incremented by 1. */ { queen_in_row[count] = col; col_free[col] = false; upward_free[count + col] = false; downward_free[count - col + board_size - 1] = false; count++; } void Queens::remove(int col) /* Pre: The square located at row count and column col is occupied by a queen. Post: The queen has been removed from the square at row count and column col; count has been decremented by 1. */ count--; col_free[col] = true; upward_free[count + col] = true; downward_free[count - col + board_size - 1] = true;

45 Queens Methods Queens::Queens(int size)
/* Post: The Queens object is set up as an empty configuration on a chessboard with size squares in each row and column. */ { board_size = size; count = 0; for (int i = 0; i < board_size; i++) col_free[i] = true; for (int j = 0; j < (2 * board_size - 1); j++) upward_free[j] = true; for (int k = 0; k < (2 * board_size - 1); k++) downward_free[k] = true; } bool Queens::unguarded(int col) const /* Post: Returns true or false according as the square in the first unoccupied row (row count) and column col is not guarded by any queen. */ return col_free[col] && upward_free[count + col] && downward_free[count - col + board_size - 1];

46 Queens Methods bool Queens::is_solved() const /* Post: Returns true if the current configuration contains board_size queens and returns false otherwise. */ { return (count == board_size); } void Queens::print() const /* Post: Prints the current configuration. */ int row, col; for (row = 0; row < count; row++){ for (col = 0; col < board_size; col++) cout << ((col == queen_in_row[row]) ? "Q" : "."); cout << endl; for (row = count; row < board_size; row++){ cout << ".";

47 Queens Main void solve_from(Queens &configuration) /* Pre: The Queens configuration represents a partially completed arrangement of nonattacking queens on a chessboard. Post: All n-queens solutions that extend the given configuration are printed. The configuration is restored to its initial state. Uses: The class Queens and the function solve_from, recursively. */ { if (configuration.is_solved()) configuration.print(); else for (int col = 0; col < configuration.board_size; col++) if (configuration.unguarded(col)) { configuration.insert(col); solve_from(configuration); // Recursively continue to add queens. configuration.remove(col); }

48 Queens Main Backtracking
void solve_from(Queens &configuration) /* Pre: The Queens configuration represents a partially completed arrangement of nonattacking queens on a chessboard. Post: All n-queens solutions that extend the given configuration are printed. The configuration is restored to its initial state. Uses: The class Queens and the function solve_from, recursively. */ { if (configuration.is_solved()) configuration.print(); else for (int col = 0; col < configuration.board_size; col++) if (configuration.unguarded(col)) { configuration.insert(col); solve_from(configuration); // Recursively continue to add queens. configuration.remove(col); }

49 Queens Main int main() /* Pre: The user enters a valid board size. Post: All solutions to the n-queens puzzle for the selected board size are printed. Uses: The class Queens and the recursive function solve_from.*/ { int board_size; print_information(); cout << "What is the size of the board?" << flush; cin >> board_size; if (board_size < 0 || board_size > max_board) cout << "The number must be between 0 and " << max_board << endl; else { Queens configuration(board_size); // initialize empty configuration solve_from(configuration); // Find all solutions extending configuration }

50 More Backtracking - Mazes
Solving a maze is another application of backtracking. Suppose we have the following maze. The goal is to find a path starting at S and ending at F that only uses white space.

51 Maze Algorithm Start at S
Look at all possible paths from the current location in a fixed order. Up Down Right Left Make one move in the chosen direction. Repeat steps 2 and 3 until F is found or a dead end is reached. If a dead end is reached, backtrack until there are unexplored options.

52 Homework – Section 5.3 (page 196)
Written E1 (written on paper) (3 pts) E2 (written on paper) (4 pts) Group Project P3 ( code) (33 pts)


Download ppt "Recursion Chapter 5."

Similar presentations


Ads by Google