420 likes | 438 Views
CS 163 Data Structures Chapter 7 C++ Simulation of Recursion. Herbert G. Mayer, PSU Status 6/8/2015. Syllabus. Definition of Recursive Algorithm Recursion vs. Iteration Fact() and Fibo (), Recursive and Iterative Q-Sequence Ackermann Function Stack Data Structure
E N D
CS 163Data StructuresChapter 7C++ Simulation of Recursion Herbert G. Mayer, PSU Status 6/8/2015
Syllabus • Definition of Recursive Algorithm • Recursion vs. Iteration • Fact() and Fibo(), Recursiveand Iterative • Q-Sequence • Ackermann Function • Stack Data Structure • Simulate Recursion via Iteration • References
Definition of Recursive Algorithm • Recall: An algorithms is recursive, if partly defined by simpler versions of itself [1] • A recursive program is the implementation of a recursive algorithm • What is the problem for a programmer, using a language that is non-recursive (e.g. standard Fortran) if the algorithm to be implemented is recursive? --See later! • What then are the other parts of a recursive algorithm, aside from the partly? • Correct recursive algorithm requires a starting point, formally known as “base case” • Base case could be multiple steps • Simpler version means: the algorithm cannot use the original call. For example, if the original call was a(n), the recursive call cannot also be a(n), but perhaps a(n-1) • 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; Fibo( n ) shown here, discussed next: • 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 strictly necessary • Recursion can replace iterative steps; yet for some people this seems counter-intuitive • Neophytes are sometimes unused to recursion; yet recursion can be as intuitive as simple iteration
Fact() and Fibo(), Recursive + Iterative • We’ll show both solutions for the factorial and the fibonacci functions • Factorial: fact1() iterative, fact2() recursive • Fibonacci: fibo1() iterative, fibo2() recursive • Sometimes the recursive algorithm is easier to see + understand, if the problem is inherently recursive • Fibonacci was Italian mathematician in Pisa, 1170 – c. 1250; introduced Hindu-Arabic numeral system to Europe • Also show the famous Towers of Hanoi
Fact() and Fibo(), Recursive + Iterative • Factorial( n ) is the product of the first n unsigned numbers, with factorial(0) defined as = 1 • Fibonacci( n ) is defined as 0 and 1 for arguments indexed 0, and 1. For all other n the result is the sum of the previous 2 Fibonacci numbers, i.e. Fibonacci( n ) = Fibonacci( n-1 ) + Fibonacci( n-2 ), for all n > 1
Fact(), Recursive and Iterative • #include <iostream.h> • unsigned fact1( unsigned arg ) // iterative • { // fact1 • unsigned int result = 1; • for ( int i = 1; i <= arg; i++ ) { • result *= i; • } //end for • return result; • } //end fact1 • unsigned fact2( unsigned arg ) // recursive • { // fact2 • if ( arg < 2 ) { • return 1; • }else{ • return arg * fact2( arg-1 ); • } //end if • } //end fact2
Fact(), Recursive and Iterative • int main( void ) • { // main • for ( int i = 0; i < 10; i++ ) { • cout << "fact1(" << i << ") = " << fact1( i ) << endl • << "fact2(" << i << ") = " << fact2( i ) << endl; • } //end for • return 0; • } //end main • fact1(0) = 1 • fact2(0) = 1 • fact1(1) = 1 • fact2(1) = 1 • fact1(2) = 2 • fact2(2) = 2 • . . . • fact1(7) = 5040 • fact2(7) = 5040 • fact1(8) = 40320 • fact2(8) = 40320 • fact1(9) = 362880 • fact2(9) = 362880
Fibo(), Recursive and Iterative • #include <iostream.h> • unsigned fibo1( unsigned arg ) // iterative • { // fibo1 • unsigned fm2 = 0; • unsigned fm1 = 1; • unsigned fm0 = 1; • for ( int i = 1; i <= arg; i++ ) { • fm2 = fm1; • fm1 = fm0; • fm0 = fm1 + fm2; • } //end for • return fm2; • } //end fibo1 • unsigned fibo2( unsigned arg ) // recursive • { // fibo2 • if ( arg < 2 ) { • return arg; • }else{ • return fibo2( arg-1 ) + fibo2( arg-2 ); • } //end if • } //end fibo2
Fibo(), Recursive and Iterative • int main( void ) • { // main • for ( int i = 0; i < 10; i++ ) { • cout << "fibo1(" << i << ") = " << fibo1( i ) << endl • << "fibo2(" << i << ") = " << fibo2( i ) << endl; • } //end for • return 0; • } //end main • fibo1(0) = 0 • fibo2(0) = 0 • fibo1(1) = 1 • fibo2(1) = 1 • fibo1(2) = 1 • fibo2(2) = 1 • fibo1(3) = 2 • fibo2(3) = 2 • fibo1(4) = 3 • fibo2(4) = 3 • . . . • fibo1(9) = 34 • fibo2(9) = 34
Replace Iteration via Recursion • What is the problem, if algorithm to be programmed is recursive, but language does not allow recursion? • Rewrite the algorithm • Or simulate recursion • Here we do the opposite: Use recursion to simulate all mathematical dyadic operations + - / * etc. • Using only functions, called recursively • Plus arithmetic increment/decrement operators ++ --and unary minus – • And conventional relational operators > >= != etc. • All other operators are dis-allowed, i.e. cannot use + - * / % **etc.
Recursion vs. Iteration: add() // return a +b without + operation! int add( int a, int b ) { // add if ( 0 == b ) { return a; }else if ( b < 0 ) { return add( --a, ++b ); }else{ return add( ++a, --b ); } //end if } //end add
Recursion vs. Iteration: sub() // return a – b; no dyadic – operation int sub( int a, int b ) { // sub return add( a, -b ); } //end sub
Recursion vs. Iteration: mult() • // return a * b, no * but add(), mult() ... • int mult( int a, int b ) • { // mult • if ( 0 == b ) { • return 0; • }else if ( 1 == b ) { • return a; • }else if ( b < 0 ) { • return -mult( a, -b ); • }else{ • // b > 0 • return add( a, mult( a, --b ) ); • } //end if • } //end mult
Recursion vs. Iteration: expo() // return a ** b, no ** op in C++; requires mult( int, int ) int expo( int a, int b ) { // expo if ( 0 == a ) { if ( 0 == b ) { printf( ”undefined value0^0\n" ); }else if ( b < 0 ) { printf( “0 to <0 power is undefined\n" ); } //end if return 0; }else if ( 0 == b ) { return 1; }else if ( 1 == a ) { return 1; }else if ( -1 == a ) { return b % 2 ? -1 : 1; }else if ( b < 0 ) { return 0; }else{ return mult( expo( a, --b ), a ); } //end if } //end expo
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: By contract, the function results of fibonacci( n )are monotonically increasing with increasing argument Results of q( n )are non-monotonic! Note # of calls: calls(q( 40 )) = 1,137,454,741
Q-Sequence, Coded in C++ • #define MAX 100 // arbitrary limit; never reached!!!! • 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 = 1Q( 2) = 1, #calls = 1Q( 3) = 2, #calls = 5Q( 4) = 3, #calls = 13Q( 5) = 3, #calls = 25Q( 6) = 4, #calls = 49Q( 7) = 5, #calls = 93Q( 8) = 5, #calls = 161Q( 9) = 6, #calls = 281Q(10) = 6, #calls = 481Q(11) = 6, #calls = 813 . . . Q(26) = 14, #calls = 1341433Q(27) = 16, #calls = 2174493Q(28) = 16, #calls = 3521137Q(29) = 16, #calls = 5700281Q(30) = 16, #calls = 9229053Q(31) = 20, #calls = 14941993Q(32) = 17, #calls = 24182797Q(33) = 17, #calls = 39137473Q(34) = 20, #calls = 63354153Q(35) = 21, #calls = 102525697Q(36) = 19, #calls = 165896537Q(37) = 20, #calls = 268460333Q(38) = 22, #calls = 434429737Q(39) = 21, #calls = 702952137Q(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 (i.e. unsigned in C++) 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 national US debt!
Ackermann Definition Students, code now in C++, volunteers shows result on white-board: 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 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( inti = 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 for return 0; } // end main
Ackermann Results For m = 0a(0,0) = 1, calls = 1. . . For m = 1. . . a(1,7) = 9, calls = 16For m = 2a(2,0) = 3, calls = 5a(2,1) = 5, calls = 14a(2,2) = 7, calls = 27a(2,3) = 9, calls = 44a(2,4) = 11, calls = 65a(2,5) = 13, calls = 90a(2,6) = 15, calls = 119a(2,7) = 17, calls = 152For m = 3a(3,0) = 5, calls = 15a(3,1) = 13, calls = 106a(3,2) = 29, calls = 541a(3,3) = 61, calls = 2432a(3,4) = 125, calls = 10307a(3,5) = 253, calls = 42438a(3,6) = 509, calls = 172233a(3,7) = 1021, calls = 693964For m = 4a(4,0) = 13, calls = 107 don’t even dream about computing a(4,2) or higher!
Stack Data Structure • High-level language functions call each other in a nested fashion, recursively, even indirectly recursively • And return in strictly the reverse order (LIFO), but with any number of further calls in between • Stack is the natural data structure to track caller and callée return information • Most programming languages allow local data in functions, of which formal parameters are just one variation • Natural to have locals also live and die on the run time stack, synchronized with call-return information
Caller pushes formals bp Field 1: Function return value Field 2: return address Stack Marker Field 3: dynamic link Field 4: static link sp 0..32 registers saved Stack Frame 0 or more locals Other temps Stack Frame of callee hp Stack grows downwards Stack grows downward
Stack Data Structure • Stack is natural data structure for recursive call: • 1.) Before call: provide (push) all actual parameters • During the call, these are the formal parameters • 2.) Then execute call, provide the return address on stack • Provide space on stack for return value if needed (function) • And push bp register, pointing to the frame of caller: known as dynamic link • 3.) Before executing callée code: allocate locals on stack • And allocate temps, e.g. copies of all regs to be used, save them and later restore before return • Thus stack grows by 3 physical + logical sections: • Actual = formal parameters, some of them just addresses • Stack Marker, AKA Activation Record: RA, RV, Dynamic Link • Locals + temps + saved registers
Stack Data Structure • Stack is addressed by 2 hardware (HW) resources, typically registers bp and sp (AKA top, or stack pointer) • It is computable to have a single register address stack via current frame, since the compiler “knows” at each place, by how much stack must have grown • Actually so done by Greenhills compilers in 1990s for register-starved Intel architecture • Base Pointer register bp points to some defined place of stack marker, typically to the dynamic link • The top of stack register sp points to the dynamically changing, momentary top of stack –dynamic during the call • The bp stays invariant during the call; changes only at further calls and at any returns –static during the call • The sp changes with each call preparation, each temp pushed on top, each intermediate result, etc.
Simulate Recursion via Iteration • Important for master programmer to understand RT-stack and recursion! • What to do, if you implement a recursive algorithm using a language that does not support recursion? • Replace 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; must use language with Goto –terrible sin
Steps of Simulating Recursion • consider directly-recursive calls of simulated function: • Define data structure struct stack_tp, to hold params, locals, etc. • Define explicit stack with top of stack (top) index, initially top=0; like a real stack identified by sp, may overflow, so include code to check; stack[ top ] holds parameters, function return value, return location (labels after a recursive call), and automatics • Define labels for each point of recursive call, more precisely at each point after the call; number these points of return, e.g. l1, l2, l3, l4 etc. There shall be branches = gotos to these points of return • At each point ofrecursive call: • Increment : i.e. top++, like HW recursion that grows and shrinks sp • Manually move parameters for “this call” onto stack; e.g. assign: stack[ top ].arg1 = actual1; stack[ top ].arg2 = actual2 . . . • Store the place of return: stack[ top ].ret = 1, or 2, or 3 alluding to l1, l2, l3 . . . • Initialize local, automatic objects: stack[ top ].local1 = value1 . . . • Jump to the function head, not including initializing code
Steps of Simulating Recursion • 5. Point of return --best to centralize one single point of return: • Decrement the top of stack: top-- • Check, to which of the stored labels the flow of control has to branch to simulate a return; e.g.: if ( stack[ top+1 ].ret == xyz ) goto label_xyz; • And if no other branch is open, then fall through to 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.: return stack[ top ].return_value; • 6. For nested recursive calls or several recursive calls in a row or both: “be creative” ; see an example later; apply these steps with meticulous precision
Simulate Recursion, fact() • #include <stdio.h> • #define MAX_STACK 100 // never reached or exceeded! • #define MAX 14 // higher factorial overflows 32bits • unsigned calls = 0; // track # of calls • typedef struct s_tp { • unsigned arg; // for formal parameters • unsigned fact; // function return value • unsigned ret; // code address after call, return! • } 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 • // there should be an assertion here! • } // end fact
Simulate Recursion, fact() • unsigned nrfact( unsigned arg ) • { // nrfact • struct_s_tp s[ MAX_STACK ]; // simulate RT stack s[]! • unsigned top = 0; // simulate sp register • 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++; // recursion! • s[ top ].arg = s[ top-1 ].arg-1; • s[ top ].ret = 2; // remember label l2 • goto l1; // now simulate recursion • l2: • // back from recursive call • top--; // sp-- • 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 • } // end 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 = 14nr_fact( 0) = 1nr_fact( 1) = 1nr_fact( 2) = 2nr_fact( 3) = 6nr_fact( 4) = 24nr_fact( 5) = 120nr_fact( 6) = 720nr_fact( 7) = 5040nr_fact( 8) = 40320nr_fact( 9) = 362880nr_fact(10) = 3628800nr_fact(11) = 39916800nr_fact(12) = 479001600nr_fact(13) = 1932053504
Simulate Recursion, fibo() #define MAX_STACK 100 // never to be reached or exceeded! #define MAX 30 // higher fibo(n) not computable! unsigned calls; // in case we track # of calls typedef struct s_tp { // type of stack unsigned arg; // copy of fibo’sarg 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 goto l1; // recurse! 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 ) { gotol3; } // end if l4: return s[ top ].fibo; // all done } // end nr_fibo
Simulate Recursion, fibo()Result r_fibo( 0) = 0, calls = 1r_fibo( 1) = 1, calls = 1r_fibo( 2) = 1, calls = 3 r_fibo( 3) = 2, calls = 5r_fibo( 4) = 3, calls = 9 . . . r_fibo(22) = 17711, calls = 57313r_fibo(23) = 28657, calls = 92735r_fibo(24) = 46368, calls = 150049r_fibo(25) = 75025, calls = 242785r_fibo(26) = 121393, calls = 392835r_fibo(27) = 196418, calls = 635621r_fibo(28) = 317811, calls = 1028457r_fibo(29) = 514229, calls = 1664079nr_fibo( 0) = 0nr_fibo( 1) = 1nr_fibo( 2) = 1nr_fibo( 3) = 2nr_fibo( 4) = 3 . . . nr_fibo(22) = 17711nr_fibo(23) = 28657nr_fibo(24) = 46368nr_fibo(25) = 75025nr_fibo(26) = 121393nr_fibo(27) = 196418nr_fibo(28) = 317811nr_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 brute-force gotos: • if ( 2 == s[ top ].ret ) { // second recursive call • goto l2; • }else if ( 3 == s[ top ].ret ) { • goto l3; // to return place • } // end if
Towers of hanoi() • The game of the “Towers of Hanoi” is a game to move a stack of n discs, while obeying certain rules • All n discs are of different sizes, residing on top of one another, a smaller disc always over a larger • 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 • 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 intmain() { // main for ( int discs = 1; discs <= MAX; discs++ ) { cout << ” hanoifor " << discs << " discs" << endl; hanoi( discs, "start", "goal ", "buff " ); cout << endl; } // end for return 0; } // end main
Towers of hanoi(), Recursive 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 2 from start to goal move disc 1 from buff to goal 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 1 from buff to goal move disc 2 from buff to start move disc 1 from goal to start move disc 3 from buff to goal move disc 1 from start to buff move disc 2 from start to goal move disc 1 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; top++; s[ top ].discs = s[ top-1 ].discs - 1; 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; goto l1; } // end if l3: if ( 2 == s[ top ].ret ) { top--; goto l2; }else if ( 3 == s[ top ].ret ) { top--; goto l3; } // end if } // end nr_hanoi
References • Douglas R. Hofstadter, “Gödel, Escher, Bach: an eternal golden braid”, Basic Books, 1999, ISBN 0465026567; came out in 1979 • Ackermann function 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 • Herbert G Mayer, Don Perkins, SIGLAN Notices, 1984, non-recursive Towers of Hanoi Solution: http://dl.acm.org/citation.cfm?id=948573