CS 201 Computer Systems Programming Chapter 6 “Recursion”

Slides:



Advertisements
Similar presentations
Computer Architecture CSCE 350
Advertisements

ECE 103 Engineering Programming Chapter 11 One Minute Synopsis Herbert G. Mayer, PSU CS Status 7/1/2014.
Lesson 19 Recursion CS1 -- John Cole1. Recursion 1. (n) The act of cursing again. 2. see recursion 3. The concept of functions which can call themselves.
Recursion. Binary search example postponed to end of lecture.
Slides prepared by Rose Williams, Binghamton University Chapter 11 Recursion.
Recursion.
CS 106 Introduction to Computer Science I 03 / 28 / 2008 Instructor: Michael Eckmann.
Copyright © 2003 Pearson Education, Inc. Slide 1.
1 CS 410 Mastery in Programming Chapter 2 Recursion Herbert G. Mayer, PSU CS status 7/3/2011.
1 CS 162 Introduction to Computer Science Chapter 10 C++ Simulation of Recursion Herbert G. Mayer, PSU Status 11/17/2014.
Recursion and Implementation of Functions
1 CS 162 Introduction to Computer Science Chapter 5 ASCII to Integer Conversion Herbert G. Mayer, PSU Status 11/9/2014.
Copyright © 2009 Pearson Education, Inc. Publishing as Pearson Addison-Wesley Chapter 19: Recursion.
1 Chapter 1 RECURSION. 2 Chapter 1  Subprogram implementation  Recursion  Designing Recursive Algorithms  Towers of Hanoi  Backtracking  Eight Queens.
Nirmalya Roy School of Electrical Engineering and Computer Science Washington State University Cpt S 122 – Data Structures Recursion Review.
 2003 Prentice Hall, Inc. All rights reserved. 1 Functions and Recursion Outline Function Templates Recursion Example Using Recursion: The Fibonacci Series.
Copyright © 2007 Pearson Education, Inc. Publishing as Pearson Addison-Wesley Chapter 12: Recursion Problem Solving, Abstraction, and Design using C++
1 CS 162 Introduction to Computer Science Chapter 4 Function Calls Herbert G. Mayer, PSU Status 11/9/2014.
1 CS 410 / 510 Mastery in Programming Chapter 2 Recursion Herbert G. Mayer, PSU CS Status 7/6/2013 With improvements suggested by: Gaby Haddock, summer.
Recursion Textbook chapter Recursive Function Call a recursive call is a function call in which the called function is the same as the one making.
Chapter 13 Recursion. Learning Objectives Recursive void Functions – Tracing recursive calls – Infinite recursion, overflows Recursive Functions that.
CSIS 123A Lecture 9 Recursion Glenn Stevenson CSIS 113A MSJC.
Recursion. What is recursion? Rules of recursion Mathematical induction The Fibonacci sequence Summary Outline.
CSC 221: Recursion. Recursion: Definition Function that solves a problem by relying on itself to compute the correct solution for a smaller version of.
Chapter 11Java: an Introduction to Computer Science & Programming - Walter Savitch 1 Chapter 11 l Basics of Recursion l Programming with Recursion Recursion.
1 CS 163 Data Structures Chapter 6 Function Calls and Recursion Herbert G. Mayer, PSU Status 6/1/2015.
CS162 - Topic #10 Lecture: Recursion –The Nature of Recursion –Tracing a Recursive Function –Work through Examples of Recursion Programming Project –Discuss.
1 Recursion Recursive function: a function that calls itself (directly or indirectly). Recursion is often a good alternative to iteration (loops). Its.
ECE 103 Engineering Programming Chapter 30 C Functions Herbert G. Mayer, PSU CS Status 8/9/2014 Initial content copied verbatim from ECE 103 material developed.
CS 536 © CS 536 Spring Introduction to Programming Languages and Compilers Charles N. Fischer Lecture 15.
Recursion CS 244 Brent M. Dingle, Ph.D. Game Design and Development Program Department of Mathematics, Statistics, and Computer Science University of Wisconsin.
Recursion.
Algorithm Analysis 1.
Recursion.
Recursion Data Structure Submitted By:- Dheeraj Kataria.
Chapter 13 Recursion Copyright © 2016 Pearson, Inc. All rights reserved.
Chapter 5 Microprocessor Runtime Stack
Chapter 19: Recursion.
to understand recursion you must understand recursion
Recursion Topic 5.
Chapter 15 Recursion.
More on Recursive Recursion vs. Iteration Why Recursion?
Chapter 15 Recursion.
More on Recursive Methods
Recursive Thinking Chapter 9 introduces the technique of recursive programming. As you have seen, recursive programming involves spotting smaller occurrences.
to understand recursion you must understand recursion
Announcements Final Exam on August 17th Wednesday at 16:00.
Recursive Thinking Chapter 9 introduces the technique of recursive programming. As you have seen, recursive programming involves spotting smaller occurrences.
Recursion "To understand recursion, one must first understand recursion." -Stephen Hawking.
Recursion Chapter 11.
Fundamentals of Programming
Stacks & Recursion.
ECE 103 Engineering Programming
CS 320 Principles of Programming Languages
Recursion Data Structures.
CS 320 Principles of Programming Languages
This Lecture Substitution model
Recursion and Implementation of Functions
Chapter 17 Recursion.
Basics of Recursion Programming with Recursion
ECE 103 Engineering Programming Chapter 19 Nested Loops
Recursion Taken from notes by Dr. Neil Moore
ECE 103 Engineering Programming Chapter 20 Change in Flow of Control
Yan Shi CS/SE 2630 Lecture Notes
ECE 103 Engineering Programming Chapter 18 Iteration
Chapter 18 Recursion.
This Lecture Substitution model
Chapter 13 Recursion Copyright © 2010 Pearson Addison-Wesley. All rights reserved.
REPETITION Why Repetition?
Presentation transcript:

CS 201 Computer Systems Programming Chapter 6 “Recursion” Herbert G. Mayer, PSU CS Status 9/27/2012

Syllabus Definition of Recursion Recursion vs. Iteration Q-Sequence Ackermann Function Simulate Recursion via Iteration References

Definition of Recursion An algorithms is recursive, if it is partly defined by simpler versions of itself [1] A recursive program is an implementation of a recursive algorithm What can user of a programming language do that is non-recursive (e.g. standard Fortran) and wishes to express a recursive algorithm in that very language? --See later! What then are the other parts of a recursive algorithm? Correct recursive algorithm requires a starting point, formally known as “base case” Base case could be multiple steps Recursive algorithm a() uses a base case as origin of computation, plus the actual function body, including some recursive use of a() Recursive body can be indirectly recursive through intermediate function a()-> b()-> a() – through intermediate function b() Primitive examples are the factorial( n ) function; or Fibonacci( n ), for non-negative arguments n; the latter shown here: Base case 1: Fibo(0) = 0 Base case 2: Fibo(1) = 1 Recursive Definition: Fibo( n ) for n > 1 = Fibo( n-1 ) + Fibo( n-2 )

Recursion vs. Iteration Iteration is expressed in programming languages by loops; e.g. for-, while-, do-, or repeat loops These are readable and efficient methods for expressing iteration, but are not necessary Recursion can easily replace such iterative steps; yet for some people this seems hard to understand In reality, neophytes are sometime unused to recursion; yet recursion can be as intuitive as the simplest iteration 

Q-Sequence, Definition Q-Sequence defined by Douglas Hofstadter in [1] as a function q(n) for positive integers n > 0 Base case n = 1: q(1) = 1 Base case n = 2: q(2) = 1 Recursive definition of q(n), for positive n > 2 q( n ) = q( n – q( n - 1 ) ) + q( n – q( n - 2 ) ) Q-Sequence reminds us of Fibonacci( n ) function, but with surprising difference in the type of result: The function results of Fibonacci( n ) are monotonically increasing with increasing argument Results of q( n ) are non-monotonic! Note also # of calls: calls(q( 40 ))=1,137,454,741

Q-Sequence, Coded in C #define MAX 100 // arbitrary limit int calls; // will be initialized each time int q( int arg ) { // q calls++; // track another call if ( arg <= 2 ) { return 1; // base case }else{ // now recurse! return q( arg - q( arg-1 ) ) + q( arg - q( arg-2 ) ); } //end if } //end q void main() { // main for( int i = 1; i < MAX; i++ ) { calls = 0; // initially no calls yet printf( "Q(%2d) = %3d, #calls = %10d\n", i, q(i), calls ); } //end for } // end main

Q-Sequence Results Q( 1) = 1, #calls = 1 Q( 2) = 1, #calls = 1 Q( 3) = 2, #calls = 5 Q( 4) = 3, #calls = 13 Q( 5) = 3, #calls = 25 Q( 6) = 4, #calls = 49 Q( 7) = 5, #calls = 93 Q( 8) = 5, #calls = 161 Q( 9) = 6, #calls = 281 Q(10) = 6, #calls = 481 Q(11) = 6, #calls = 813 . . . Q(26) = 14, #calls = 1341433 Q(27) = 16, #calls = 2174493 Q(28) = 16, #calls = 3521137 Q(29) = 16, #calls = 5700281 Q(30) = 16, #calls = 9229053 Q(31) = 20, #calls = 14941993 Q(32) = 17, #calls = 24182797 Q(33) = 17, #calls = 39137473 Q(34) = 20, #calls = 63354153 Q(35) = 21, #calls = 102525697 Q(36) = 19, #calls = 165896537 Q(37) = 20, #calls = 268460333 Q(38) = 22, #calls = 434429737 Q(39) = 21, #calls = 702952137 Q(40) = 22, #calls = 1137454741 . . . Will never reach Q(100) in your life time 

Ackermann Definition Ackermann a( m, n ) is defined as a function of two non-negative integers m and n Base case 1: a( 0, n ) = n + 1 Base case 2: a( m, 0 ) = a( m - 1, 1 ) Recursive definition of a( m, n ), m, n > 0 a( m, n ) = a( m - 1, a( m, n - 1 ) ) Ackermann complexity grows awfully fast; e.g. a(4,2) is an integer number with 19,729 decimal digits; greater than the US debt!

Ackermann Coded in C unsigned a( unsigned m, unsigned n ) { // a calls++; // global unsigned if ( 0 == m ) { // note operand order return n + 1; // first base case }else if ( 0 == n ) { // m > 0 return a( m - 1, 1 ); // other base case }else{ // m > 0, n > 0 return a( m-1, a( m, n-1 ) ); // recurse! } //end if } //end q void main() { // main for( int i = 0; i < MAX; i++ ) { printf( "\nFor m = %d\n", i ); for( int j = 0; j < MAX; j++ ) { calls = 0; printf( "a(%1d,%1d) = %10u, calls = %12u\n", i, j, a( i, j ), calls ); } //end for } // end main

Ackermann Results For m = 0 a(0,0) = 1, calls = 1 . . . a(1,7) = 9, calls = 16 For m = 2 a(2,0) = 3, calls = 5 a(2,1) = 5, calls = 14 a(2,2) = 7, calls = 27 a(2,3) = 9, calls = 44 a(2,4) = 11, calls = 65 a(2,5) = 13, calls = 90 a(2,6) = 15, calls = 119 a(2,7) = 17, calls = 152 For m = 3 a(3,0) = 5, calls = 15 a(3,1) = 13, calls = 106 a(3,2) = 29, calls = 541 a(3,3) = 61, calls = 2432 a(3,4) = 125, calls = 10307 a(3,5) = 253, calls = 42438 a(3,6) = 509, calls = 172233 a(3,7) = 1021, calls = 693964 For m = 4 a(4,0) = 13, calls = 107 don’t even dream about computing a(4,2)  or higher!

Simulate Recursion via Iteration Important to understand for systems programmer What to do, if you implement a recursive algorithm using a language that does not support recursion? Replace the recursive by a non-recursive algorithm! Or simulate recursion via non-recursive methods After all, a computer chip has no notion of recursion; it is a sequential machine that “simulates recursion” via non-recursive methods; the compiler plus run- time system perform this transformation! Done so at local industry in the past: FPS used Fortran to implement System SW and compilers Here are the actual steps of simulating recursion via iteration; good to use a language with Goto:

Steps of Simulating Recursion consider directly-recursive calls, i.e. calls from within recursive function: Define explicit stack with top of stack (tos) index, initially tos=0; like a real stack, it may overflow, you better include code to check; holds all parameters, function return value, return location (labels after a recursive call), and automatic locals Define labels for each point of recursive call, more precisely at each point of return; number these labels, for example l1, l2, l3 etc. There will be branches to these points of return At each point of recursive call: Increment the tos: tos++ Manually move parameters for “this call” onto stack; e.g. stack[ tos ].arg1 = … stack[ tos ].arg2 = … Store the place of return: stack[ tos ].ret = 1, or 2, or 3 alluding to l1, l2, l3 Initialize local, automatic objects Jump to the head of the function, not including initializing code

Steps of Simulating Recursion 4. Ideally, all explicitly coded returns and the implied return at the end of the recursive function body can be re-coded into a single place; if not, the code before each return is replicated: Decrement the top of stack index: tos-- Check, to which of the stored labels the flow of control has to branch=goto to continue execution; e.g.: if ( stack[ tos ].ret == xyz ) goto label_xyz; And if no other branch is open, then fall through the end For void functions this is a literal fall-through For true functions, the return value has to be computed before the fall-through, e.g.: stack[ tos ].return_val = … 5. For nested recursive calls or several recursive calls in a row or both: “be creative”  ; see an example later

Simulate Recursion, fact() #include <stdio.h> #define MAX_STACK 100 // never reached or exceeded! #define MAX 14 // higher factorial overflows 32bits unsigned calls; // track # of calls typedef struct s_tp { unsigned arg; unsigned fact; unsigned ret; } struct_s_tp; // first the recursive fact() function for reference // includes tracking # of calls unsigned fact( unsigned arg ) { // fact calls++; // gotta be global if ( 0 == arg ) { // why strange order? return 1; }else{ return fact( arg - 1 ) * arg; } //end if } //end fact

Simulate Recursion, fact() unsigned nrfact( unsigned arg ) { // nrfact struct_s_tp s[ MAX_STACK ]; // local stack, no recursion! unsigned top = 0; s[ top ].arg = arg; // this call’s argument s[ top ].ret = 3; // 3 alludes to label l3 l1: if ( 0 == s[ top ].arg ) { s[ top ].fact = 1; }else{ top++; // recurse soon s[ top ].arg = s[ top-1 ].arg-1; s[ top ].ret = 2; // remember label l2 goto l1; // here simulate recursion l2: // back from recursive call. top--; // back from call s[ top ].fact = s[ top + 1 ].fact * s[ top ].arg; } //end if if ( s[ top ].ret == 2 ) { // test, where to branch to goto l2; // unstructured goto into if l3: return s[ top ].fact; } //end nrfact

Simulate Recursion, fact() Result r_fact( 0) = 1, calls = 1 r_fact( 1) = 1, calls = 2 r_fact( 2) = 2, calls = 3 r_fact( 3) = 6, calls = 4 r_fact( 4) = 24, calls = 5 r_fact( 5) = 120, calls = 6 r_fact( 6) = 720, calls = 7 r_fact( 7) = 5040, calls = 8 r_fact( 8) = 40320, calls = 9 r_fact( 9) = 362880, calls = 10 r_fact(10) = 3628800, calls = 11 r_fact(11) = 39916800, calls = 12 r_fact(12) = 479001600, calls = 13 r_fact(13) = 1932053504, calls = 14 nr_fact( 0) = 1 nr_fact( 1) = 1 nr_fact( 2) = 2 nr_fact( 3) = 6 nr_fact( 4) = 24 nr_fact( 5) = 120 nr_fact( 6) = 720 nr_fact( 7) = 5040 nr_fact( 8) = 40320 nr_fact( 9) = 362880 nr_fact(10) = 3628800 nr_fact(11) = 39916800 nr_fact(12) = 479001600 nr_fact(13) = 1932053504

Simulate Recursion, fibo() #define MAX_STACK 100 // never to be reached or exceeded! #define MAX 30 // higher fibo(n) not computed unsigned calls; // in case we track # of calls typedef struct s_tp { // type of stack unsigned arg; // copy of fibo’s arg unsigned fibo; // return value for fibo unsigned ret; // to which label to goto? } struct_s_tp; // recursive function for reference: unsigned fibo( unsigned arg ) { // fibo calls++; if ( arg <= 1 ) { // base case? return arg; // if so: done }else{ return fibo( arg-1 ) + fibo( arg-2 ); } //end if } //end fibo

Simulate Recursion, fibo() unsigned nr_fibo( unsigned arg ) { //nr_fibo struct_s_tp s[ MAX_STACK ]; // stack can be local unsigned top = 0; // initially s[ top ].arg = arg; // copy arg to stack s[ top ].ret = 4; // if all fails, return l1: if ( s[ top ].arg <= 1 ) { s[ top ].fibo = s[ top ].arg; }else{ top++; // ready to recurse s[ top ].arg = s[ top - 1 ].arg - 1; s[ top ].ret = 2; // to place of 1. return goto l1; // recurse l2: top++; // ready to recurse again s[ top ].arg = s[ top - 2 ].arg - 2; s[ top ].ret = 3; // to place of 2nd return l3: // two returns simulated top -= 2; // simulate 2 returns s[ top ].fibo = s[ top+1 ].fibo + s[ top+2 ].fibo; } //end if if ( 2 == s[ top ].ret ) { // second recursive call goto l2; }else if ( 3 == s[ top ].ret ) { goto l3; l4: return s[ top ].fibo; // all done } //end nr_fibo

Simulate Recursion, fibo() Result r_fibo( 0) = 0, calls = 1 r_fibo( 1) = 1, calls = 1 r_fibo( 2) = 1, calls = 3 r_fibo( 3) = 2, calls = 5 r_fibo( 4) = 3, calls = 9 . . . r_fibo(22) = 17711, calls = 57313 r_fibo(23) = 28657, calls = 92735 r_fibo(24) = 46368, calls = 150049 r_fibo(25) = 75025, calls = 242785 r_fibo(26) = 121393, calls = 392835 r_fibo(27) = 196418, calls = 635621 r_fibo(28) = 317811, calls = 1028457 r_fibo(29) = 514229, calls = 1664079 nr_fibo( 0) = 0 nr_fibo( 1) = 1 nr_fibo( 2) = 1 nr_fibo( 3) = 2 nr_fibo( 4) = 3 . . . nr_fibo(22) = 17711 nr_fibo(23) = 28657 nr_fibo(24) = 46368 nr_fibo(25) = 75025 nr_fibo(26) = 121393 nr_fibo(27) = 196418 nr_fibo(28) = 317811 nr_fibo(29) = 514229

Simulating Return of fibo() Must the computation of the continuation place be after the if-statement? Or can we relocate it into the Else-Clause? That would lead to a partial simulation, in which only the case arg > 1 continues correctly Yet even cases for arg <= 1 must compute the right continuation via (unstructured) brute-force gotos: if ( 2 == s[ top ].ret ) { // second recursive call goto l2; }else if ( 3 == s[ top ].ret ) { goto l3; } //end if

Simulate Recursion, fibo2() unsigned nr_fibo2( unsigned arg ) { //nr_fibo2 struct_s_tp s[ MAX_STACK ]; // stack can be local unsigned top = 0; // initially s[ top ].arg = arg; // copy arg to stack s[ top ].ret = 4; // if all fails, return l1: if ( s[ top ].arg <= 1 ) { s[ top ].fibo = s[ top ].arg; if ( 2 == s[ top ].ret ) { // second recursive call goto l2; }else if ( 3 == s[ top ].ret ) { goto l3; } //end if }else{ top++; // ready to recurse s[ top ].arg = s[ top - 1 ].arg - 1; s[ top ].ret = 2; // to place of 1. return goto l1; // recurse l2: top++; // ready to recurse again s[ top ].arg = s[ top - 2 ].arg - 2; s[ top ].ret = 3; // to place of 2nd return l3: // two returns simulated top -= 2; // simulate 2 returns s[ top ].fibo = s[ top+1 ].fibo + s[ top+2 ].fibo; l4: return s[ top ].fibo; // all done } //end nr_fibo2

Towers of Hanoi The game of the “Towers of Hanoi” is a game to move a stack of discs, while obeying certain rules All n discs are of different sizes, residing on top of one another, always a smaller disc on top of a larger one The goal is to move the whole tower from start, to the goal position, using one additional buffer location But only moving 1 single disc at a time And never placing a larger disc on top of a smaller one During various times, any disc may be placed on the start position, the goal, or the buffer

Towers of hanoi(), Recursive #include <iostream.h> #define MAX … some small integer < 32 void hanoi( int discs, char* start, char* goal, char* buff ) { // hanoi if ( discs > 0 ){ hanoi( discs-1, start, buff, goal ); cout << "move disc " << discs << " from " << start << " to “ << goal << endl; hanoi( discs-1, buff, goal, start ); } //end if } // end hanoi int main() { // main for ( int discs = 1; discs <= MAX; discs++ ) { cout << ” hanoi for " << discs << " discs" << endl; hanoi( discs, "start", "goal ", "buff " ); cout << endl; } //end for return 0; } //end main

Towers of hanoi(), Output Rec. move disc 1 from start to goal < For 1 disc move disc 1 from start to buff < For 2 discs move disc 2 from start to goal move disc 1 from buff to goal move disc 1 from start to goal < For 3 discs move disc 2 from start to buff move disc 1 from goal to buff move disc 3 from start to goal move disc 1 from buff to start move disc 2 from buff to goal move disc 1 from start to goal move disc 1 from start to buff < For 4 discs move disc 3 from start to buff move disc 1 from goal to start move disc 2 from goal to buff move disc 1 from start to buff move disc 4 from start to goal move disc 2 from buff to start move disc 3 from buff to goal

Simulate Recursion, hanoi() void nr_hanoi( unsigned discs, char* start, char* goal, char* buff ) { // nr_hanoi struct_h_type s[ MAX_STACK ]; unsigned top = 0; s[ top ].discs = discs; s[ top ].start = start; s[ top ].buff = buff; s[ top ].goal = goal; s[ top ].ret = 4; l1: if ( s[ top ].discs > 0 ) { top++; s[ top ].discs = s[ top-1 ].discs - 1; s[ top ].start = s[ top-1 ].start; s[ top ].buff = s[ top-1 ].goal; s[ top ].goal = s[ top-1 ].buff; s[ top ].ret = 2; goto l1; l2: cout << "nr move disc “ << s[ top ].discs << “ from “ << s[ top ].start << “ to “ << s[ top ].goal << endl; s[ top ].start = s[ top-1 ].buff; s[ top ].buff = s[ top-1 ].start; s[ top ].goal = s[ top-1 ].goal; s[ top ].ret = 3; } //end if l3: if ( 2 == s[ top ].ret ) { top--; goto l2; }else if ( 3 == s[ top ].ret ) { goto l3; } //end nr_hanoi

References Douglas R. Hofstadter, “Gödel, Escher, Bach: an eternal golden braid”, Basic Books, 1999, ISBN 0465026567 Ackermann functiona at NIST: http://xlinux.nist.gov/dads/HTML/ackermann.html Herbert G Mayer: “Advanced C Programming on the IBM PC”, 1989, Windcrest, ISBN 0-8306-9163-4 Non-recursive solution to Towers of Hanoi: http://portal.acm.org/citation.cfm?id=948602