1 / 49

Chapter 7 Recursion

Chapter 7 Recursion. 7.1 Recursive Definitions. When defining infinite sets, giving a complete list of elements is impossible, and for large finite sets, it is inefficient. ---->Therefore, a recursive definition is used. Example: the set of natural numbers, N 1. 0  N ;

Download Presentation

Chapter 7 Recursion

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Chapter 7 Recursion 7.1 Recursive Definitions When defining infinite sets, giving a complete list of elements is impossible, and for large finite sets, it is inefficient. ---->Therefore, a recursive definition is used. Example: the set of natural numbers, N 1. 0N; 2. If n  N, then (n+1)  N; 3. There are no other objects in the set N.

  2. Chapter 7 Recursion 7.1 Recursive Definitions A recursive definition consists of two parts. In the first part, called the anchor or the ground case, the basic elements that are the building blocks of all other elements of the set are listed. In the second part, rules are given that allow for the construction of new objects out of basic elements or objects that have already been constructed.

  3. Chapter 7 Recursion 7.1 Recursive Definitions Recursive definitions serve two purposes: generating new elements and testing whether or not an element belongs to a set. Recursive definitions are frequently used to define functions and sequences of numbers. For example, Factorial function

  4. Chapter 7 Recursion 7.1 Recursive Definitions Recursive definitions of sequences have one undesirable feature: in order to determine the value of an element sn of a sequence, we first have to compute the values of some or all of the previous elements, s1, s2, …, sn-1. Computationally, this is undesirable since it forces us to make calculations in a roundabout way. Therefore, we want to find an equivalent definition, or formula, that makes no references to other elements of the sequence. For example, can be converted into the simple formula g(n)=2n.

  5. Chapter 7 Recursion 7.1 Recursive Definitions In computer science, one area where recursive definitions are used extensively is in the specification of the grammars of programming languages. Grammar is specified either in terms of block diagrams or in terms of the Backus-Naur Form (BNF). For example, the syntactic definition of a statement in C can be represented in BNF as: <statement>::= while (<expression>) <statement> | if (<expression>) <statement>| if (<expression>) <statement> else<statement>| ...

  6. Chapter 7 Recursion 7.1 Recursive Definitions Another area in which recursive definitions are used is in programming. The good news is that virtually no effort is needed to make the transition from a recursive definition of a function to its implementation in C. factorial (int n) { if (n == 0) return 1; else return n*factorial(n-1); } recursive function call implemented using a run-time stack by operating system

  7. Chapter 7 Recursion 7.2 Function Calls and Recursion Implementation What happens when a function is called? 1. If the function has formal parameters, they have to be initialized to the values passed as actual parameters. 2. The system has to know where to resume execution of the program after the function has finished. Where to store the return address and how much space is needed? For a function call, more information has to be stored that just a return address.

  8. Chapter 7 Recursion 7.2 Function Calls and Recursion Implementation If function f1() which contains a declaration of an automatic (local) variable x calls function f2() which locally declares the variable x, the system has to make a distinction between these two x’s. When f1()=f2(), i.e., when a function calls itself recursively, how does the system make a distinction between these two variables x?

  9. Chapter 7 Recursion 7.2 Function Calls and Recursion Implementation The state of each function, including main(), is characterized by the contents of all automatic variables, by the values of the function’s parameters, and by the return address indicating where to restart its caller. The data area containing all this information is called an activation record or stack frame and is allocated on the run-time stack. An activation record exists for as long as a function owning it has not completed execution.

  10. Chapter 7 Recursion 7.2 Function Calls and Recursion Implementation Contents of run-time stack, when main() calls function f1(), f1() calls f2(), and f2() calls f3(). As can be seen, there is no problem if f1()=f2()=f3(). 1

  11. Chapter 7 Recursion 7.2 Function Calls and Recursion Implementation • An activation record usually contains the following information: • values for all parameters to the function; location of the first cell, if an array is passed, and copies of all other data items • local (automatic) variables which can be stored elsewhere, in which case, the activation record would contain only their descriptors and pointers to the locations where they are stored

  12. Chapter 7 Recursion 7.2 Function Calls and Recursion Implementation • An activation record usually contains the following information: • the return address to resume control by the caller, the address of the caller’s instruction immediately following the call • a dynamic link, which is a pointer to the caller’s activation record (Why is this needed?) • the returned value for a function not declared as void. Since the size of the activation record may vary from one call to another, the returned value is placed right above the activation record of the caller

  13. Chapter 7 Recursion 7.3 Anatomy of a Recursive Call Example: A C function: /* 102 */ float power (float x, int n) { /* 103 */ if (!n) /* if ( n == 0) */ /* 104 */ return 1; /* 105 */ return x*power(x,n-1); }

  14. Chapter 7 Recursion 7.3 Anatomy of a Recursive Call Main() { … /* 136 */ y=power(5.6, 2); … } A trace of the recursive call: call 1 power(5.6,2) call 2 power(5.6,1) call 3 power(5.6,0) call 3 returns 1 call 2 return2 5.6 call 1 returns 31.36

  15. Chapter 7 Recursion 7.3 Anatomy of a Recursive Call Changes to the run-time stack during execution of power(5.6,2)

  16. Chapter 7 Recursion 7.3 Anatomy of a Recursive Call The function power() can be implemented without using any recursion: float nonrecpower(float x, int n) { float result = 1; if (n>0) for (result=x; n>1; --n) result *= x; return result; }

  17. Chapter 7 Recursion 7.3 Anatomy of a Recursive Call Do we gain anything by using recursion instead of a loop? 1. More intuitive 2. Increase program readability 3. Improve self-documentation 4. Simplify coding (usually the code is shorter) However, if coded correctly, the non-recursive version may be more efficient (run faster)!

  18. Chapter 7 Recursion 7.4 Tail Recursion All recursive definitions contain a reference to a set or function being defined. There are, however, a variety of ways such a reference can be implemented. There may be many possible levels of recursion, or different levels of complexity. We start with the simplest case, tail recursion.

  19. Chapter 7 Recursion 7.4 Tail Recursion Tail recursion is characterized by the use of only one recursive call at the very end of a function implementation. In other words, when the call is made, there are no statements left to be executed by the function; the recursive call is not only the last statement, but there are no earlier recursive calls, direct or indirectly.

  20. Chapter 7 Recursion 7.4 Tail Recursion Example: void tail (int i) { if (i>0) { printf(“%d “, i); tail(i-1); } } Tail recursion is simply a glorified loop and can be easily replaced by such. void IterativeEquivalentofTail(int i) { for ( ; i>0; i--) printf(“%d “, i); }

  21. Chapter 7 Recursion 7.4 Tail Recursion Is there any advantage in using tail recursion over iteration? For languages such as C there may be no compelling advantage, but in a language such as Prolog, which has no explicit loop construct (loops are simulated by recursion), tail recursion acquires a much greater weight. In languages endowed with a loop or its equivalents, such as if statement combined with a goto statement, tail recursion is not a recommendable feature.

  22. Chapter 7 Recursion 7.5 Non-Tail Recursion Example: Print an input line in reverse order. void /* 200 */ reverse() /* 201 */ { int ch; /* 202 */ if ( (ch=getchar()) != ‘\n’) { /* 203 */ reverse(); /* 204 */ putchar(ch); } Where is the trick?

  23. Chapter 7 Recursion 7.5 Non-Tail Recursion Run-time stack when main() calls reverse() and the input is the string: “Is it going to work?”

  24. Chapter 7 Recursion 7.5 Non-Tail Recursion A non-recursive version of the same reverse function: void SimpleIterativeReverse() { char stack[80]; register int top; gets (stack); for (top=strlen(stack)-1; top>=0; putchar(stack[top--])); }

  25. Chapter 7 Recursion 7.5 Non-Tail Recursion Another non-recursive version of the same reverse function: void IterativeReverse() /* without using gets() and strlen() */ { int stack[80]; register int top=0; while ((stack[top++]=getchar()) != ‘\n’); for (top -= 2; top>=0; putchar(stack[top--])); } We are just making explicit (in the non-recursive version) what is done implicitly by the system (in the recursive version) by using a stack.

  26. Chapter 7 Recursion 7.5 Non-Tail Recursion If there are a system stack available: NonRecursiveReverse() { int ch; clear_stack(S); while ((ch=getchar()) != ‘\n’) push(ch,S); while (!empty(S)) putchar(pop(S)); }

  27. Chapter 7 Recursion 7.5 Non-Tail Recursion One way or the other, the transformation of recursion into iteration usually involves the explicit handling of a stack. Furthermore, when converting a function from a recursive into an iterative version, program clarity can be diminished and the brevity of program formulation lost.

  28. Chapter 7 Recursion 7.6 Indirect Recursion A function f() can call itself indirectly via a chain of other calls. f() can also call itself indirectly through different chains. Example: receive() stores the incoming information in a buffer, decode() converts it to legible form, and store() stores it in a file. After store() accomplishes its task, it calls receive() to intercept more encoded information using the same buffer. Therefore, we have the chain of calls: receive()decode() store() receive() decode() store()…

  29. Chapter 7 Recursion 7.6 Indirect Recursion receive(buffer) while bufferis not filled up ifinformation is still incoming get a character and store it inbuffer; else exit(); decode(buffer); decode(buffer) decode information inbuffer; store(buffer); store(buffer) transfer information frombufferto file; receive(buffer);

  30. Chapter 7 Recursion 7.7 Nested Recursion A more complicated case of recursion is found in definitions in which a function is not only defined in terms of itself but it is also used as one of the parameters. Example: h(1)=h(2+h(2))=h(14)=14 h(2)=h(2+h(4))=h(12)=12 h(3)=h(2+h(6))=h(2+6)=h(8)=8 h(4)=h(2+h(8))=h(2+8)=h(10)=10

  31. Chapter 7 Recursion 7.7 Nested Recursion The Ackermann function This function is interesting because of its remarkably rapid growth. It grows so fast that it is guaranteed not to have a representation by a formula that uses arithmetical operations such as addition, multiplication, and exponentiation.

  32. Chapter 7 Recursion 7.7 Nested Recursion The Ackermann function A(0,0)=0+1=1,A(0,1)=2,A(1,0)=A(0,0)=1, A(1,1)=A(0,A(1,0))=A(0,1)=2 A(1,2)=A(0,A(1,1))=A(0,2)=3 A(2,1)=A(1,A(2,0))=A(1,A(1,0))=A(1,1)=2 A(3,m)=A(2,A(3,m-1))=A(2,A(2,A(3,m-2)))=…=2m+3-3

  33. Chapter 7 Recursion 7.8 Excessive Recursion Logical simplicity and readability are used as an argument supporting the use of recursion. The price for using recursion is slowing down execution time and storing on the run-time stack more things than required in a non-recursive approach. Example: The Fibonacci numbers Fibonacci(n) { if (n<2) return 1; else return Fibonacci(n-1)+Fibonacci(n-2); }

  34. Chapter 7 Recursion 7.8 Excessive Recursion Many repeated computations

  35. Chapter 7 Recursion 7.8 Excessive Recursion

  36. Chapter 7 Recursion 7.8 Excessive Recursion IterativeFib(int n) { if (n < 2) return n; else { register int i = 2, tmp, current = 1, last = 0; for ( ; i<=n; ++i) { tmp= current; current += last; last = tmp; } return current; } }

  37. Chapter 7 Recursion 7.8 Excessive Recursion

  38. Chapter 7 Recursion 7.8 Excessive Recursion We can also solve the recurrence relation: Can be neglected when n is large an-an-1-an-2=0 The characteristic equation: x2-x-1=0

  39. Chapter 7 Recursion 7.9 Backtracking Backtracking allows us to systematically try all available avenues from a certain point after some of them lead to nowhere. Using backtracking, we can always return to a position which offers other possibilities for successfully solving the problem.

  40. Chapter 7 Recursion 7.9 Backtracking The Eight Queens Problem

  41. Chapter 7 Recursion 7.9 Backtracking The Eight Queens Problem Pseudo code of the backtracking algorithm PutQueen(row) forevery positioncolon the same row ifpositioncol is available { place the next queen in positioncol; if (row < 8) PutQueen(row+1); elsesuccess; remove the queen from positioncol; /* backtrack */ }

  42. Chapter 7 Recursion 7.9 Backtracking The Eight Queens Problem Natural Implementation Q 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 Q 0 0 0 0 0 0 0 0 0 Q 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 1 1 1 1 1 0 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0 1 1 1 1 0 1 0 1 0 0 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 1 1 1 0 1 0 1 1 0 0 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 1 1 0 1 0 1 1 1 0 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 0 1 0 1 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 The second queen Initialization The first queen

  43. Chapter 7 Recursion 7.9 Backtracking The Eight Queens Problem Natural Implementation Q 0 0 0 0 0 0 0 Q 0 0 0 0 0 0 0 Q 0 0 0 0 0 0 0 0 0 Q 0 0 0 0 0 0 0 Q 0 0 0 0 0 0 0 Q 0 0 0 0 0 0 0 0 0 Q 0 0 0 0 0 0 0 Q 0 0 0 0 0 0 0 Q 0 0 0 0 1 0 0 0 1 1 1 0 Q 0 0 0 0 0 0 0 Q 0 0 0 0 0 0 0 1 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 Q 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 Q 0 1 0 1 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 1 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 The third queen The fourth queen The 5th & 6th queen Have to backtrack now! This would be queen now.

  44. Chapter 7 Recursion 7.9 Backtracking The Eight Queens Problem Natural Implementation The setting and resetting part would be the most time-consuming part of this implementation. However, if we focus solely on the queens, we can consider the chessboard from their perspective. For the queens, the board is not divided into squares, but into rows, columns, and diagonals.

  45. Chapter 7 Recursion 7.9 Backtracking The Eight Queens Problem Simplified data structure A 4 by 4 chessboard Row-column = constant for each diagonal

  46. Chapter 7 Recursion 7.9 Backtracking The Eight Queens Problem

  47. Chapter 7 Recursion 7.9 Backtracking The Eight Queens Problem

  48. Chapter 7 Recursion 7.9 Backtracking The Eight Queens Problem

  49. Chapter 7 Recursion Exercise: 1. Ex. 5 in page 139. 2. Ex. 3 in page 142.

More Related