420 likes | 648 Views
CSE 30331 Lecture 5 – Exceptions, Recursion. Exceptions – quick review Recursion Power Function Towers of Hanoi Fibonacci Numbers Recursive Algorithm Dynamic Programming Approach Iterative Solution Permutations Eight Queens Backtracking Summary. Exceptions.
E N D
CSE 30331Lecture 5 – Exceptions, Recursion • Exceptions – quick review • Recursion • Power Function • Towers of Hanoi • Fibonacci Numbers • Recursive Algorithm • Dynamic Programming Approach • Iterative Solution • Permutations • Eight Queens • Backtracking • Summary
Exceptions • When something unexpected happens in a block of code, and the proper handling of that event depends on where the call to the block came from … • Throw an exception • Expect the calling code to determine and take the proper action • Otherwise, use standard techniques, • if(error) act; in the code where the error occurs
Standard Exceptions • Defined in the following headers are … • #include <exception> • exception and bad_exception, the base classes • #include <new> • bad_alloc • #include <typeinfo> • bad_cast and bad_typeid • #include <stdexcept> • logic_error, out_of_range, length_error, invalid_argument, domain_error, runtime_error, range_error, overflow_error, and underflow_error • #include <ios> • ios_base::failure
Throwing a standard exception • In a function we can throw an error and set its message string as shown below … #include <stdexcept> double myFunct(int index) { ... if (index < 0) throw out_of_range(“in MyFunct() : index < 0”); ... }
What happens when the exception is thrown? • Once the exception is thrown … • ALL following code in the same block is SKIPPED • Control returns to the calling code, and … • ALL code following the call is SKIPPED • This process of backing out of the call stack continues UNTIL … • A call is found in a TRY block, and then … • ALL code is skipped in the rest of the block, BUT … • Code execution continues in the appropriate CATCH block that follows the TRY… • UNLESS none is found, in which case eventually the program TERMINATES with a generic “unhandled” exception message
Try & Catch • In code that calls a function which might generate an exception … • Place the call in a try block • Follow the code with a catch block that will deal appropriately with the error • Appropriate may be nothing more than output of the exception message, and a hasty exit of the program
Try & Catch Example try { ... cout << “Which value?:”; cin >> num; x = myFunct(num); // this might throw an exception // the rest of this code in block may get skipped cout << x; ... } catch (out_of_range& e) { // every std exception has a what() method that // returns its message cout << e.what(); exit(1); }
Try & Multiple Catches try { while (...) { try { ... x = myOtherFunct(num); // might throw several exceptions ... } catch (out_of_range& e) { // exception was a simple out of range error cout << e.what(); // inform user and ... continue; // try again -- get a better input } ... } } catch (exception& e) { // if exception was not a range error, abort cout << e.what(); exit(2); }
Try & Multiple Catches - catch and throw again (except.cpp) try { while (...) { try { x = myOtherFunct(num); // might throw several exceptions } catch (out_of_range& e) { cout << e.what(); // inform user and ... continue; // try again -- get a better input } catch (runtime_error& e) { // note: if catch (exception& e) here and throw, message lost throw e; // <--- don’t want to handle it here, so throw again } } } catch (exception& e) { // if exception was not a range error, abort cout << e.what(); exit(2); }
Making your own exceptions • Best way is to customize your exception from one of the standard ones • Use inheritance • For examples, See … • Ford & Topp, pp 68-74 • Josutis, pp 25-31
Recursive Algorithms • A recursive function generally implements a recursive algorithm. Both function and algorithm must consists of … • 1. One or more stopping conditions (base cases) that can be directly evaluated for certain arguments. • 2. One or more recursive steps in which a current value of the function can be computed by calling the function with arguments that will eventually arrive at a stopping condition.
Recursive Definition of the Power Function • A recursive definition of xn distinguishes between … • n = 0 (starting point) where xn = x0 = 1 and • n 1 where xn can be calculated using the calculated value of xn-1
Implementing the Recursive Power Function // pre: n is non-negative double power(double x, int n) { if (n == 0) // base case return 1.0; else // recursive step return x * power(x,n-1); }
The Towers of Hanoi • Given a stack of 64 disks of decreasing size • Initially stacked on peg 1 of three pegs • Move the stack, one disk at a time to peg 3 • Using peg 2 as needed • Such that you NEVER have a larger disk on top of a smaller disk • NOTE: When the problem is solved the world will end!!!!! So say the priests of the Temple of Brahma.
Towers of Hanoi void hanoi (int n, char src, char dest, char spare) { if (n > 1) { hanoi(n-1,src,spare,dest); cout << “move ” << src << “ to ” << dest << ‘\n’; hanoi(n-1,spare,dest,src); } else // last disk (n == 1) cout << “move ” << src << “ to ” << dest << ‘\n’; }
Efficiency – Towers of Hanoi • The algorithm is exponential • T(2) = 2T(1)+1 = 3 = 22-1 • T(3) = 2T(2)+1 = 7 = 23-1 • T(4) = 2T(3)+1 = 15 = 24-1 • T(n) = 2n-1 • So T(64) = 264 (about 500 billion years) • Maybe the priests were right!
Recursive Definition of the Fibonacci numbers • A recursive definition of fib(n) distinguishes between … • n = 0 or 1 (starting points) where fib(n) = n and • n > 1 where fib(n) can be calculated using the calculated values of fib(n-1) and fib(n-2)
Fibonacci Numbers(Recursive solution) int fib (int n) { if ((n == 0) || (n == 1)) // base cases return n; else // general case // (sum of previous two values) return (fib(n-1) + fib(n-2)); } The sequence of Fibonacci numbers. {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...}
How many calls is that? It’s exponential, Θ(Φn) • Fibonacci numbers are related to the GoldenRatio Φ • The efficiency of the recursive algorithm is … • Expanding that we get the series for n >= 2 • 3 5 9 15 25 41 67 109 … • Which is the same as … T(n)=2*fib(n+1)-1 • or …
How many calls is that? It’s exponential, Θ(Φn) • Examining the cost versus the values • n ...... 0 1 2 3 4 5 6 7 8 9 10 • fib(N).. 0 1 1 2 3 5 8 13 21 34 55 • T(n).... 3 5 9 15 25 41 67 109 • We see that • T(n) = 2*fib(n+1)-1 = 2*Φn+1 - 1 • or …
Dynamic Programming • Basic idea • Remember values computed in previous steps for use in later steps • Avoids recalculation of previously determined values • Trims tree of recursive calls significantly • (See tree of fib(5) on following slide)
Affects of Using Dynamic Programming on fib(5) Circles:saved values used Gray:calls avoided
Fibonacci with Dynamic Programming // pre: fibList contains n values, all -1 int fibDyn (int n, vector<int>& fibList) { int fibValue; if(fibList[n] >= 0) // previously computed return fibList[n]; if (n <= 1) // base cases (0 & 1) fibValue = n; else fibValue = fibDyn(n-1,fibList) + fibDyn(n-2,fibList); fibList[n] = fibValue; // save value in fibList return fibValue; }
Fibonacci Numbers using Iteration int fibiter (int n) // pre: n >= 0 { int oneback = 1, twoback = 1, current; if (n == 0 || n == 1) // base cases return n; else // compute successive terms for (int i = 3; i <= n; i++) { current = oneback + twoback; twoback = oneback; oneback = current; } return current; }
Permutations …Recursive Approach • Permutations of a list of n values include all possible re-orderings of those values • The number of permutations is n! • A vector (permList) holds the values • At each level of recursion (i), n-i recursive calls are made • Prior to each recursive call permList[i] is swapped with permList[j], for all i < j < n • The permutation prefix is permList[0]..permList[i-1] • The swapping of permList[i] and permList[j] effectively creates all possible prefixes perList[0]..permList[i] differing in only the last value • Each recursive call then finds all list permutations beginning with the given prefix and containing some permutation of the suffix permList[i+1].. permList[n-1]
Permutations void permute (vector<int> permList, int index) { // permList must be value parameter int vSize = permList.size(); if (index == vsize-1) writeList(permList); // show result else { permute(permList, index+1); // permute tail of list for (int i=index+1; i<vSize; i++) { int temp = permList[index]; // swap last prefix val permList[index] = perList[i]; permList[i] = temp; // shuffle positions index+1 .. vsize-1 permute(permList, index+1); // permute tail of list } } }
Permutations • This is a sequence of calls to find permutations of [4 5 8] • Permutation prefixes are underlined P([4 5 8],0) P([4 5 8],1) P([4 5 8],2) – writes out 4 5 8 P([4 8 5],2) – writes out 4 8 5 P([5 4 8],1) P([5 4 8],2) – writes out 5 4 8 P([5 8 4],2) – writes out 5 8 4 P([8 5 4],1) P([8 5 4],2) – writes out 8 5 4 P([8 4 5],2) – writes out 8 4 5
The 8-Queens Problem • How can 8 queens be placed on a chess board such that none of them are under attack? • Basic Strategy: • Place a queen at top of column 0 • Place a queen on row r = 0 of column 1 • While under attack, move queen to next row … • Place a queen in row r = 0 of column 2 … • Whenever queen cannot be paced anywhere in the next column, back up to previous column, move queen further down, and continue …
The 8-Queens Example (placing queen in column 4, …) Starting here Place next queen at (5,6) Then no place in column 7 is free So back up, no other free place in column 6 So back up again No other free place in column 5 So back up again Place queen at (3,4) and move forward again Or if we ever get here ...
Eight Queens bool placeQ (vector<int>& qList, int col) { bool found = false; if (col == 8) found = true; // all 8 queens placed else { int row = 0; while (row < 8 && !found) // place queen in column { if (safeLoc(row,col,qList)) { qList[col] = row; // place queen on row found = placeQ(qList,col+1); // place the rest? if (!found) row++; // try next row this column } else row++; // not safe, move queen to next row } } return found; // found complete solution? }
Eight Queens bool queens (vector<int>& qList, int row) { qList[0] = row; // start at board[row,0] return placeQ(qList,1); // continue in column 1 } bool safeLoc (int row, int col, const vector<int>& qList) { for (int qCol = 0; qCol < col; qCol++) { int qRow = qList[qCol]; if ((qRow == row) || (qCol-qRow == col-row) || (qCol+qRow == col+row) ) return false; // on same row or diagonal } return true; // safe, not under attack }
8-Queens … • Each solution is a sequence of 8 numbers, 0..7, representing the queen’s position in each column • There are 88 = (23)8 = 224 or 4+ million such arrangements of queens, but … • Queens cannot be on the same row, so … • There are only 8! = 40,320 arrangements that might actually be solutions • How many actual solutions are there? Only 92. • How might we measure the algorithm efficiency?
From Wikipedia • Brute-force algorithm that places a single queen on each row, leads to only 88 = 224 = 16,777,216 blind placements. • It is possible to do much better than this. • One algorithm generates the permutations of the numbers 1 through 8 (of which there are 8! = 40,320), and uses the elements of each permutation as indices to place a queen on each row, guaranteeing no rook attacks. Then it rejects those boards with diagonal attacking positions. • The backtrackingdepth-first search program, a slight improvement on the permutation method, constructs the search tree by considering one row of the board at a time, eliminating most non-solution board positions at a very early stage in their construction. Because it rejects rook and diagonal attacks even on incomplete boards, it examines only 15,720 possible queen placements. • A further improvement which examines only 5,508 possible queen placements is to combine the permutation based method with the early pruning method: the permutations are generated depth-first, and the search space is pruned if the partial permutation produces a diagonal attack. Constraint programming can also be very effective on this problem.
Knight’s Tour • Similar to the 8-queens problem • For an nxn board, n >= 1 • Is there a sequence of moves that a knight can follow that will systematically visit every board position, without visiting any position more than once? • Approach: make a move, then another, … • When no moves possible, back up and make an alternative move from the previous position • Continue extending path and retreating until • No solution is possible, knight backs off of board • A solution is found
0 1 2 4 5 6 7 3 0 0 3 8 11 1 7 10 5 2 ? 4 1 ? 9 ? 2 3 6 4 5 6 7 Knight’s Tour Example Now, what will be move #12? (1,5) or (2,4) or (2,2)
Summary • An algorithm is recursive if it applies itself to the solution of smaller problems of the same type. • Eventually, these problems must lead to one or more stopping conditions (base cases). • The solution at a stopping condition leads to the solution of previous problems. • A recursive C++ function calls itself.
Summary … • Dynamic Programming (top down) • Fibonacci function • Uses a vector to store Fibonacci numbers as a recursive function computes them • Avoids redundant recursive calls and leads to an O(n) algorithm to find the nth Fibonacci number. • Recursive Fibonacci function that does not apply dynamic programming has exponential running time.
Summary … • Backtracking Algorithm (8 Queens) • Find a consistent partial solution • (place a queen safely in current column) • try to recursively extend the partial solution to a complete solution • (place queen in next column …) • If recursive step fails to find a complete solution, it returns and the algorithm tries again from a new consistent partial solution. • (replace queen in current column & continue, or return to previous column if necessary)