# Andreas Savva Data Structures Chapter 5 Recursion.

## Presentation on theme: "Andreas Savva Data Structures Chapter 5 Recursion."— Presentation transcript:

Andreas Savva Data Structures Chapter 5 Recursion

Stack Frames for Subprograms
D B C A M Time Tree of Subprogram Calls Start Finish M A D B C

Tree-Diagram Definitions
The circles in a tree diagram are called vertices or nodes. The top of the tree is called its root. The vertices immediately below a given vertex are called the children of that vertex. The (unique) vertex immediately above a given vertex is called its parent. (The root is the only vertex in the tree that has no parent) The line connecting two vertices is called a branch. Siblings are vertices with the same parent. A vertex with no children is called a leaf or an external vertex. Two branches of a tree are adjacent if the lower vertex of the first branch is the upper vertex of the second. A sequence of branches in which each is adjacent to its successor is called a path. The height of a tree is the number of vertices on a longest possible path from the root to a leaf. (Hence a tree containing only one vertex has height 1.) The depth or level of a vertex is the number of branches on a path from the root to the vertex.

Recursion Every recursive process consists of two parts:
A base case that is processed without recursion. A general method that reduces a particular case to one or more of the smaller cases, making progress towards eventually reducing the problem all the way to the base case.

Mathematical Recursion
Base case: f(0) = 0 Recursion: f(n) = n + f(n-1) for n > 0 f(5) = 5 + f(4) = f(3) = f(2) = f(1) = f(0) = = 15

A Recursive Definition - Factorial
int factorial(int n) // Pre: n is a nonnegative integer // Post: Return the value of the factorial of n { if (n == 0) return 1; else return n * factorial(n - 1); }

Factorial(4) 24 int factorial(int n) { if (n == 0) return 1; else
Memory int factorial(int n) { if (n == 0) return 1; else return n * factorial(n-1); } void main() cout << factorial(4); f(0) n = 1 1 f(1) n 1 = 1* f(0) = 1 * 1 = 1 f(2) n 2 = 2 * f(1) = 2 * 1 = 2 2 f(3) n 3 = 3 * 2 = 6 = 3 * f(2) 6 f(4) n 4 = 4 * 6 = 24 = 4 * f(3) 24 24 main()

Recursive Procedure void Display() { int n; cin >> n;
<enter> 8 12 3 1 Memory void Display() { int n; cin >> n; if (n != -999) Display(); cout << n << ’ ’; } void main() Display n -999 Display n 8 Display n 12 Display n 3 Display n 1 main

Recursive Procedure #include <iostream.h>
Memory #include <iostream.h> void display(int n, char c) { if (n != 0) cout << c; display(n-1,c+1); } void main() display(4,’A’); display n c ’E’ display n 1 c ’D’ display n 2 c ’C’ display n 3 c ’B’ display n 4 c ’A’ A B C D main

Exercise #include <iostream.h>
void display(int n, int m, char c) { if (n==1 && m==1) cout << c; else if (n > 1) cout << endl; display(n-1,m,c); } else if (m > 1) cout << ’ ’; display(n,m-1,c); void main() display(3,4,’7’); 7 1 2 3 4 5 6

Function Power – n r Base case: Key step: int power(int n, int r)
// Pre: r is a nonnegative integer // Post: Return the value of n to the power r { if (r == 0) return 1; else return n * power(n, r - 1); }

Function Power 16 n 2 r = 1 1 n 2 r 1 = 2 * 1 = 2 = 2 * f(2,0) 2 n 2 r
Memory #include <iostream.h> int power(int n, int r) { if (r == 0) return 1; else return n * power(n,r-1); } void main() cout << power(2,4); f(2,0) n 2 r = 1 1 f(2,1) n 2 r 1 = 2 * 1 = 2 = 2 * f(2,0) 2 f(2,2) n 2 r = 2 * f(2,1) = 2 * 2 = 4 4 f(2,3) n 2 r 3 = 2 * 4 = 8 = 2 * f(2,2) 8 f(2,4) n 2 r 4 = 2 * f(2,3) = 2 * 8 = 16 16 16 main()

Divide and Conquer: The Towers of Hanoi
Developed in the 19th century. The problem: At the creation of the world, the priests in the temple of Brahma were given a brass platform on which were 3 diamond needles. On the first needle there were stacked 64 golden disks, each one slightly smaller than the one under it. The priests were assigned the task of moving all the golden disks from the first needle to the third, subject to the conditions that: only one disk can be moved at a time, and no disk is allowed to be placed on top of a smaller disk. The priest were told that when they had finished moving the 64 disks, it would be the end of the world.

“Move 64 disks from tower 1 to tower 3 using tower 2 as temporary
The Towers of Hanoi 1 2 3 move(64,1,3,2) means “Move 64 disks from tower 1 to tower 3 using tower 2 as temporary

The Solution move(63,1,2,3) // Move 63 disks from tower 1 to tower 2
cout << ”Move disk 64 from tower 1 to tower 3” << endl; move(63,2,3,1) // Move 63 disks from tower 2 to tower 3 1 2 3 63 62 . . . 4 3 2 1 63 62 . . . 4 3 2 1 63 62 . . . 4 3 2 1 64 64

The Program const int disks = 64;
void move(int count, int start, int finish, int temp) { if (count > 0) { move(count – 1, start, temp, finish); cout << ”Move disk ” << count << ” from ” << start << ” to “ << finish << endl; move(count – 1 , temp, finish, start); } int main() { move(disks, 1, 3, 2); return 0; disks = 3 1 2 3

Analysis (disks = 3) Move disk 1 from tower 1 to tower 3

No of Moving Disks (disks=64)
No of recursions: … = … = 265 – 1 = 36,893,488,147,419,103,231 No of moving disks: Excluding the calls with count == 0, which do nothing: … = 264 – 1 = 18,446,744,073,709,551,615 No of Seconds in a year:  32,000,000 No of years the priests need to solve the problem if they move one disk in every second:  576,460,752,303

Designing Recursive Algorithms
Find the key step. Begin by asking yourself, “How can this problem be divided into parts?” Find a stopping rule. This stopping rule is usually the small, special case that is trivial or easy to handle without recursion. Combine the stopping rule and the key step, using an if-statement to select between them. Check termination. Verify that the recursion will always terminate. Be sure that your algorithm correctly handles extreme cases. Draw a recursion tree. The height of the tree is closely related to the amount of memory that the program will require, and the total size of the tree reflects the number of times the key step will be done.

When not to use Recursion
Consider the following two functions for calculating factorial: Recursive: int factorial(int n) { if (n == 0) return 1; else return n * factorial(n − 1); } Non-recursive: int count, product = 1; for (count = 1; count <= n; count++) product *= count; return product;

factorial(5) - Recursive
Example: factorial(5) = 5 * factorial(4) = 5 * (4 * factorial(3)) = 5 * (4 * (3 * factorial(2))) = 5 * (4 * (3 * (2 * factorial(1)))) = 5 * (4 * (3 * (2 * (1 * factorial(0))))) = 5 * (4 * (3 * (2 * (1 * 1)))) = 5 * (4 * (3 * (2 * 1))) = 5 * (4 * (3 * 2)) = 5 * (4 * 6) = 5 * 24 = 120

Question Which of the two functions uses less storage space?
The recursive one has one variable The iterative one has three variables But actually the recursive program will set up a stack and fill it in with n+1 numbers: n, n-1, n-2, … , 2, 1, 0 Thus, the recursive function keeps more storage, and it will take more time as well, since it must store and retrieve all the numbers as well as multiply them.

Fibonacci recursive function
Definition: Fibonacci numbers are denoted by the recurrence relation F0 = 0, F1 = 1, Fn = Fn−1 + Fn−2 for n ≥ 2 term 0th 1st 2nd 3rd 4th 5th 6th 7th 8th 1 2 3 5 8 13 21 int fibonacci(int n) { if (n == 0) return 0; else if (n == 1) return 1; else return fibonacci(n-1) + fibonacci(n-2); }

No of calculations of F(n) grows exponentially with n
fibonacci(7) No of calculations of F(n) grows exponentially with n

Fibonacci iterative function
int fibonacci(int n) { } No of calculations of F(n) increases linearly in n

Comparison between Recursion and Iteration

Towers of Hanoi without tail recursion
const int disks = 64; void move(int count, int start, int finish, int temp) { int swap; // temporary storage to swap towers while (count > 0) { // Replace the if statement with a loop. move(count – 1, start, temp, finish); // first recursive call cout << ”Move disk ” << count << ” from ” << start << ” to “ << finish << endl; count--; // Change parameters to mimic the second recursive call. swap = start; start = temp; temp = swap; } int main() { move(disks, 1, 3, 2); return 0;

Towers of Hanoi (disks=64)
With one recursive call: No of recursions = … + 1 = 2080 No of variables = No of Recursions * 5 = 10,400 No of variables needed at one time = tree height * 5 = 64 * 5 = 320 With two recursive calls: No of recursions = 36,893,488,147,419,103,231 No of variables = No of Recursions * 4 = 147,573,952,589,676,412,924 No of variables needed at one time = tree height * 4 = 65 * 4 = 260

Exercise 1 What is the output of the following program?
#include <iostream> using namespace std; void display(int n) { if (n != 0) cout << n; display(n-1); } void main() display(5);

Exercise 2 Write a recursive function “Sum” that will take an integer number n and it will return the summation of all the numbers between 1 and n. If n ≤ 0 the function should return zero. Examples: Sum(4) = = 10 Sum(0) = 0 Sum(-4) = 0 Write the same function without recursion.

Exercise 3 Write a recursive function “Summation” that will take two integer numbers n and m and it will return the summation of all the numbers between n and m. If n > m the function should return zero. Examples: Summation(3, 7) = = 25 Summation(7, 2) = 0 Summation(4,4) = 4 Summation(-3,1) = -5 Write the same function without recursion.

Exercise 4 Write a recursive procedure “Display” that will take an integer number n and display all the numbers between 1 and n in reverse order. If n ≤ 0 it should not display anything. Examples: Display(5); will display: Display(3); will display: Write the same function without recursion.

Exercise 5 Write a recursive function “Multiply” that will take two integer numbers n and m and it will return the multiplication of all the numbers between n and m. If n > m the function should return zero. Examples: Multiply(2, 5) = 2 × 3 × 4 × 5 = 120 Multiply(7, 2) = 0 Multiply(4, 4) = 4 Multiply(-3, 1) = 0 Write the same function without recursion.

Exercise 6 Write a recursive function “SumEven” that will take two integer numbers n and m and it will return the summation of all the even numbers between n and m. If n > m the function should return zero. Examples: SumEven(3, 10) = = 28 SumEven(7, 2) = 0 SumEven(4,4) = 4 SumEven(3,3) = 0 Write the same function without recursion.

Exercise 7 The greatest common divisor (gcd) of two positive integers is the largest integer that divides both of them. i.e. gcd(8,12) = 4 gcd(9,18) = 9 gcd(16,25) = 1 Write a non-recursive function, gcd(x,y) that searches through the positive integers until it finds the largest integer dividing both x and y. Write a recursive function, gcd(x,y) that implements Euclid’s algorithm: if y = 0, then the gcd of x and y is x; otherwise the gcd of x and y is the same as the gcd of y and x % y. Rewrite the function of part (b) into iterative form. Discuss the advantages and disadvantages of each of these methods.

Exercise 8 The binomial coefficients can be defined by the following recurrence relation, which is the idea of Pascal’s triangle. C(n, 0) = 1 C(n, n) = 1 for n ≥ 0 C(n, k) = C(n-1, k) + C(n-1, k-1) for n > k > 0

Exercise 8 (continue) Write a recursive function to generate C(n, k) by the formula. Draw the recursion tree for calculating C(6, 4). Use the square array with n indicating the row and k the column, and write a non-recursive program to generate Pascal’s triangle in the lower left half of the array, that is, in the entries for which k ≤ n. Write a non recursive program that uses neither an array nor a stack to calculate C(n, k) for arbitrary n ≥ k ≥ 0. Determine the approximate space and time requirements for each of the algorithms devised in (a), (c), and (d).

Exercise 9 What is the output of the following program?
#include <iostream> using namespace std; void display(int n) { if (n > 0) display(n-1); cout << n; } void main() display(4);

Exercise 10 What is the output of the following program?
#include <iostream> using namespace std; void display(int n) { if (n > 0) display(n-1); cout << n; } void main() display(4);

Exercise 11 What is the output of the following program?
#include <iostream> using namespace std; void display(int n) { if (n > 0) display(n-1); display(n-2); cout << n; } void main() display(4);

Exercise 12 What is the output of the following program?
#include <iostream> using namespace std; void display(int n) { if (n > 0) display(n-1); display(n-2); cout << n; } void main() display(4);

Exercise 13 An integer is printed with commas inserted every 3 positions from the right. That is, to print the number as 12,345,678, the 678 cannot be printed until after the preceding part of the number is printed. Write a recursive function “PrintWithCommas” that will print its long integer parameter with commas inserted properly. You must be careful with leading zeros to ensure, for example, that the number 12,003 is printed properly.

Backtracking – The Eight-Queen Puzzle
Problem: How to place eight queens on a chessboard so that no queen can take another. Even the great C. F. Gauss did not obtain a complete solution when he considered it in 1850. Solutions

Algorithm A more complex recursive application
The choice of data structure can affect a recursive program Example: 4 Queens (4 x 4 Board)

Analysis of Backtracking
8 Queens Check all the cells Amount of work = = 4,426,165,368 Only one queen in each row Amount of work = … = 88 = 16,777,216 Only one queen in each column Amount of work = … = 8! = 40,320 64 8 8 1 8 times 8 1 7 6

The Main Program void main() { int board_size;
cout << ”What is the size of the board? ”; cin >> board_size; if (board_size < 1 || board_size > max_board) cout << ”The number must be between 1 and “ << max_board << endl; else Queens configuration(board_size); solve(configuration); }

The Recursion void solve(Queens &configuration) {
if (configuration.is_solved()) confiquration.print(); cout << endl; } else for (int col = 0; col < configuration.board_size; col++) if (configuration.unguarded(col)) configuration.insert(col); solve(configuration); // Recursively continue to add queens configuration.remove(col);

The Queens class const max_board = 30; class Queens { public:
Queens(int size); bool is_solved() const; void print() const; bool unguarded(int col) const; void insert(int col); void remove(int col); int board_size; private: int count; // current number of queens = first unoccupied row bool queen_square[max_board][max_board]; }

The Constructor Queens::Queens(int size) { board_size = size;
count = 0; for (int row = 0; row < board_size; row++) for (int col = 0; col < board_size; col++) queen_square[row][col] = false; }

Is_Solved & Print +Q++ +++Q Q+++ ++Q+ bool Queens::is_solved() const {
return count == board_size; } void Queens::print() const { for (int row = 0; row < board_size; row++) for (int col = 0; col < board_size; col++) if (queen_square[row][col]) cout << ”Q”; else cout << ”+”; cout << endl; } +Q++ +++Q Q+++ ++Q+

Insert & Remove void Queens::insert(int col) {
queen_square[count++][col] = true; } void Queens::remove(int col) { queen_square[--count][col] = false; }

Unguarded

Unguarded bool Queens::unguarted(int col) const { int i;
bool ok = true; for (i = 0; ok && i < count; i++) ok = !queen_square[i][col]; // check upper part of column for (i = 1; ok && count – i >= 0 && col – i >= 0; i++) ok = !queen_square[count – i][col – i]; // check upper-left diagonal for (i = 1; ok && count – i >= 0 && col + i < board_size; i++) ok = !queen_square[count – i][col + i]; // check upper-right diagonal return ok; }

Review and Refinement Size 8 9 10 11 12 13
Number of solutions ,680 14,200 73,712 Time (seconds) Time per solution (ms.)

The Revised Queens class
const max_board = 30; class Queens { public: Queens(int size); bool is_solved() const; void print() const; bool unguarded(int col) const; void insert(int col); void remove(int col); int board_size; private: int count; bool col_free[max_board]; bool upwart_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 }

The Constructor Queens::Queens(int size) { int i; board_size = size;
count = 0; for (i = 0; i < board_size; i++) col_free[i] = true; for (i = 0; i < (2 * board_size – 1); i++) upward_free[i] = downward_free[i] = true; }

Insert void Queens::insert(int col) { queen_in_row[count] = col;
col_free[col] = false; upward_free[count + col] = false; downward_free[count – col + board_size – 1] = false; count++; }

Remove void Queens::remove(int col) { count--; col_free[col] = true;
upward_free[count + col] = true; downward_free[count – col + board_size – 1] = true; }

Unguarded bool Queens::unguarted(int col) const {
return col_free[col] && upward_free[count + col] && downward_free[count – col + board_size – 1]; } Homework: is_solved() print()

Review and Refinement Size 8 9 10 11 12 13
First program: Size Number of solutions ,680 14,200 73,712 Time (seconds) Time per solution (ms.) Second program: Size Number of solutions ,680 14,200 73,712 Time (seconds) Time per solution (ms.)

Game-tree (two players) Even levels denote the legal moves of the first player (root is at level 0) Odd levels denote the legal moves of the second player Example: The game of Eight: The first player chooses one of the numbers 1, 2, or 3. At each later turn the appropriate player chooses one of 1, 2, or 3, but the number chosen by the previous player is not allowed. A running sum of the numbers chosen is kept, and if a player brings this sum to exactly eight, then the player wins. If the player takes the sum over eight, then the other player wins.

Tree for the game of Eight
1ST 1 2 3 2ND 2 3 1 3 1 2 1ST 1 3 1 2 2ND 2 3 1 2 2 3 1 3 8 S 8 S 9 F 1ST 1 3 1 2 2 3 1 3 2 3 9 S 8 F 9 S 9 S 10 S 8 F 10 S 9 S 10 S 2ND 2 3 9 F 10 F

Evaluation Function Examine the current situation and return a value assessing its benefits. Usually large integers reflect favorable situations for the first player, and small (or more negative) show an advantage for the second player.

The Minimax Method 1ST The move 2ND 1ST 2ND -3 S -3 S 3 F 1ST -2 S 2 F
Evaluation function 1st win +ve 2nd win -ve 2ND 2 3 1 F 1 F

The Minimax Method 1 -2 -3 -2 -3 -2 -3 1ST (max) 2ND (min) 1ST (max) 1
Should play

Tic-Tac-Toe

The Board class Board { public: Board(); bool done() const;
void print() const; void Instructions() const; bool better(int value,int old_value) const; void play(Move try_it); int worst_case() const; int evaluate() const; int legal_moves(Stack &moves) const; void operator = (const Board &original); int the_winner() const; private: int squares[3][3]; int moves_done; }; class Move { public: Move(int r, int c); int row; int col; }; Move::Move(int r, int c) { row = r; col = c; }

Data Structure 2 1 0 – Empty 1 – Player 1 2 – Player 2 moves_done = 6

Player 1 always starts first
Board methods Board::Board() { for (int i=0; i<3; i++) for (int j=0; j<3; j++) squares[i][j] = 0; moves_done = 0; } void Board::play(Move try_it) { squares[try_it.row][try_it.col] = moves_done % 2 + 1; moves_done++; bool Board::better(int a, int b) const { if (moves_done % 2 == 0) return a>=b; else return a<=b; int Board::worst_case() const { return -10; return 10; Player 1 always starts first

int Board::the_winner() const {
int i; for(i = 0; i < MaxBoard; i++) if (squares[i][0] && squares[i][0] == squares[i][1] && squares[i][0] == squares[i][2]) return squares[i][0]; if (squares[0][i] && squares[0][i] == squares[1][i] && squares[0][i] == squares[2][i]) return squares[0][i]; if (squares[0][0] && squares[0][0] == squares[1][1] && squares[0][0] == squares[2][2]) return squares[0][0]; if (squares[0][2] && squares[0][2] == squares[1][1] && squares[2][0] == squares[0][2]) return squares[0][2]; return 0; }

int Board::legal_moves(Stack &moves) const {
int count = 0; while(!moves.empty()) moves.pop(); for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) if (squares[i][j] == 0) { Move can_play(i,j); moves.push(can_play); count++; } return count; bool Board::done() const { return moves_done == 9 || the_winner() > 0; int Board::evaluate() const { int winner = the_winner(); if (winner == 1) return 10 - moves_done; else if (winner == 2) return moves_done - 10; else return 0; void Board::print() const { cout << "Move " << moves_done << endl; for (int i=0; i<3; i++) for (int j=0; j<3; j++) cout << squares[i][j]; cout << endl; } void Board::operator = (const Board &original) for (int i=0;i<3;i++) for (int j=0;j<3;j++) squares[i][j] = original.squares[i][j]; moves_done = original.moves_done;

Look Ahead int look_ahead(const Board &game, int depth, Move &recommented) { if (game.done() || depth == 0) return game.evaluate(); else { Stack moves; game.legal_moves(moves); int value, best_value = game.worst_case(); while (!moves.empty()) { Move try_it, reply; moves.top(try_it); Board new_game = game; new_game.play(try_it); value = look_ahead(new_game,depth - 1, reply); if (game.better(value,best_value)) { best_value = value; recommented = try_it; } moves.pop(); return best_value;

Player 1 is a human player
The main program void main() { Board game; int depth = 5; Move recomented; int count = 0; while (!game.done()) { if (count % 2 == 0) { cout << "Enter a Move: "; cin >> recomented.row >> recomented.col; game.play(recomented); } else { look_ahead(game,depth,recomented); count++; game.print(); if (game.the_winner() == 1) cout << "You Won" << endl; else if (game.the_winner()==2) cout << "You Lost" << endl; else cout << “It is a Draw" << endl; Player 1 is a human player Player 2 is the computer