Presentation is loading. Please wait.

Presentation is loading. Please wait.

Lecture 11 Recursive Thinking CSC212 Data Structure

Similar presentations


Presentation on theme: "Lecture 11 Recursive Thinking CSC212 Data Structure"— Presentation transcript:

1 Lecture 11 Recursive Thinking CSC212 Data Structure
Instructor: George Wolberg Department of Computer Science City College of New York

2 Outline of This Lecture
Start with an Example of Recursion “racing car” – not in the textbook using slides (provided by the authors) Recursive Thinking: General Form Tracing Recursive Calls using blackboard to show the concepts A Closer Look at Recursion activation record and runtime stack

3 Recursive Thinking Chapter 9 introduces the technique of recursive programming. As you have seen, recursive programming involves spotting smaller occurrences of a problem within the problem itself. This presentation gives an additional example, which is not in the book. This lecture demonstrates a typical pattern that arises in recursive functions. The lecture can be given shortly before or shortly after the students have read Section 9.1. By the way, the book delays the introduction of recursion until just before trees. Our reasoning here is that recursive functions with linked lists can sometimes be bad programming practice because the run-time stack must grow to the length of the linked list (and iterative algorithms are usually just as easy). On the other hand, the depth of recursive tree algorithms is merely the depth of the tree (and equivalent iterative algorithms can be harder to program). Data Structures and Other Objects Using C++

4 A Car Object To start the example, think about your favorite family car In this lecture, we're going to design a recursive function which manipulates a new object called a Car. In order to explain the Car that I have in mind, think about your favorite family car... NOTE: In Powerpoint, the next few slides will automatically appear every few seconds...

5 A Car Object To start the example, think about your favorite family car ...let's try for something a bit more modern that this...

6 A Car Object To start the example, think about your favorite family car ...no, that's too conservative...

7 A Car Object To start the example, think about your favorite family car ...that's better!

8 A Car Object To start the example, think about your favorite family car Imagine that the car is controlled by a radio signal from a computer I want you to imagine that this family car is being controlled by a radio signal coming from your computer.

9 A Car Class To start the example, think about your favorite family car
Imagine that the car is controlled by a radio signal from a computer The radio signals are generated by activating member functions of a Car object class Car { public: . . . }; The radio signals themselves are generated by writing a program which uses a new object type called a Car. Each time one of the car methods is activated, the computer sends a radio signal to control the car. This may seem a bit far-fetched, but such radio-controlled hardware really does exist, and the hardware is controlled by programs which could have objects such as these. A similar example is an automatic baggage system at an airport, which is controlled by a computer. Also, remember that in order to understand this example, you don't need to know all the details of how the Car's methods work. All you need to know is what each of the Car's methods accomplishes. I have four particular methods in mind, and I'll describe what each method does.

10 Member Functions for the Car Class
class Car { public: Car(int car_number); void move( ); void turn_around( ); bool is_blocked( ); private: { We don't need to know the private fields! } . . . }; Here are the prototypes of the four member functions. Let's look at each method in turn...

11 The Constructor When we declare a Car
and activate the constructor, the computer makes a radio link with a car that has a particular number. int main( ) { Car racer(7); . . . This example demonstrates the usage of the constructor. In this example, we have declared a Car called racer and the activated the constructor with the argument 7. The argument 7 just indicates which of several cars we want to control. Each car needs to have a unique number. In this example, we are making a radio connection to "Car Number 7". Any future radio signals generated by activating racer's methods will control "Car Number 7".

12 The turn_around Function
When we activate turn_around, the computer signals the car to turn 180 degrees. int main( ) { Car racer(7); racer.turn_around( ); . . . After the connection is made, we can activate other member functions of racer. In this example, we are activating racer.turn_around( ); The function sends a radio signal to the car, telling it to turn 180 degrees. (In case you hadn't noticed, the car has a very small turning radius!)

13 The turn_around Function
When we activate turn_around, the computer signals the car to turn 180 degrees. int main( ) { Car racer(7); racer.turn_around( ); . . . After the connection is made, we can activate other member functions of racer. In this example, we are activating racer.turn_around( ); The function sends a radio signal to the car, telling it to turn 180 degrees. (In case you hadn't noticed, the car has a very small turning radius!)

14 The move Function When we activate move, the computer signals the car to move forward one foot. int main( ) { Car racer(7); racer.turn_around( ); racer.move( ); . . . Here's an example of the third Car member function in action. When we activate racer.move( ); the result is a radio signal which tells the car to move forward one foot. NOTE: In Powerpoint, the next slide automatically appears, moving the car forward "one foot".

15 The move Function When we activate move, the computer signals the car to move forward one foot. int main( ) { Car racer(7); racer.turn_around( ); racer.move( ); . . . As you can see, the car has moved forward by a foot.

16 The is_blocked( ) Function
The is_blocked member function detects barriers. int main( ) { Car racer(7); racer.turn_around( ); racer.move( ); if (racer.is_blocked( ) ) cout << "Cannot move!"; . . . The last Car member function is a boolean function called is_blocked. The function returns true if there is some barrier in front of the car, making it impossible to move. The function returns false if the front of the car is unblocked, making a move possible. A question: What will racer.is_blocked return in this example? Answer: True, because there is a definite barrier in front of the car. Another question: Can you state a good precondition for the move member function? Answer: is_blocked must return false.

17 Your Mission Write a function which will move a Car forward until it reaches a barrier... Now we can examine the problem that I have in mind. I want to write a function which will move a Car forward until it reaches a barrier...

18 Your Mission Write a function which will move a Car forward until it reaches a barrier... ...as shown here. NOTE: In Powerpoint, this slide automatically appears after six seconds. Each of the next few slides automatically appears after a few more seconds.

19 Your Mission Write a function which will move a Car forward until it reaches a barrier... Here, you can see that the barrier has been reached. But that is not the end of the function’s work. The function must next...

20 Your Mission Write a function which will move a Car forward until it reaches a barrier... ...then the car is turned around... ...turn the car around...

21 Your Mission Write a function which will move a Car forward until it reaches a barrier... ...then the car is turned around... ...and returned to its original location, facing the opposite way. ...and move the car back to its original position, facing the opposite direction.

22 Your Mission Write a function which will move a Car forward until it reaches a barrier... ...then the car is turned around... ...and returned to its original location, facing the opposite way. This is the end of the function’s work.

23 Your Mission void ricochet(Car& moving_car); Write a function which will move a Car forward until it reaches a barrier... ...then the car is turned around... ...and returned to its original location, facing the opposite way. The function, called ricochet, has the heading shown here. There is one parameter, called moving_car, which is the Car that the function manipulates. We will assume that the radio connection has already been made for this car, so all the function has to do is issue the necessary move and turn_around commands.

24 Pseudocode for ricochet
void ricochet(Car& moving_car); if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. The first action the function takes is to check for a very simple case: the case where the car is already blocked. In this case, no movement is needed. All that the function needs to do is turn the car around, and leave it in the same place. NOTE: The lecturer can pretend to be the car, blocked at a wall, to demonstrate this simple case.

25 Pseudocode for ricochet
void ricochet(Car& moving_car); if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: On the other hand, if the car is not blocked, then some movement is needed. The function begins the movement by moving the car forward just one foot. Notice that the move activation is not in a loop. We're just moving one foot, with the primary goal being to make a smaller version of the original problem. In fact, once the car has moved just a single foot forward, we do have a smaller version of the original problem... moving_car.move( ); . . .

26 Pseudocode for ricochet
void ricochet(Car& moving_car); This makes the problem a bit smaller. For example, if the car started 100 feet from the barrier... if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: ...as shown in this example. In this example, the car starts 100 feet from a barrier. moving_car.move( ); . . . 100 ft.

27 Pseudocode for ricochet
void ricochet(Car& moving_car); This makes the problem a bit smaller. For example, if the car started 100 feet from the barrier... then after activating move once, the distance is only 99 feet. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: After moving just one foot, the car is 99 feet from the barrier. moving_car.move( ); . . . 99 ft.

28 Pseudocode for ricochet
void ricochet(Car& moving_car); We now have a smaller version of the same problem that we started with. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: Since 99 feet is less than 100 feet, we have a smaller version of the original problem. moving_car.move( ); . . . 99 ft.

29 Pseudocode for ricochet
void ricochet(Car& moving_car); Make a recursive call to solve the smaller problem. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: Once a smaller problem has been created, we can make the key step of any recursive function: The ricochet function calls itself to solve the smaller problem! The act of a function calling itself is recursion, or in other words a recursive call. In order for recursion to succeed, the recursive call must be asked to solve a problem which is somehow smaller than the original problem. The precise meaning of "smaller" is given in Section 9.3, but for now you can use your intuition about what a "smaller" problem is. moving_car.move( ); ricochet(moving_car); . . . 99 ft.

30 Pseudocode for ricochet
void ricochet(Car& moving_car); The recursive call will solve the smaller problem. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: The key idea to remember is this: When you make a recursive call to solve a smaller problem, the recursive call will successfully solve that smaller problem. In this case, that means that the recursive call will move the car 99 feet to the barrier... moving_car.move( ); ricochet(moving_car); . . . 99 ft.

31 Pseudocode for ricochet
void ricochet(Car& moving_car); The recursive call will solve the smaller problem. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: ...as shown here and in the next few slides. moving_car.move( ); ricochet(moving_car); . . .

32 Pseudocode for ricochet
void ricochet(Car& moving_car); The recursive call will solve the smaller problem. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: This is still work being done in the recursive call. moving_car.move( ); ricochet(moving_car); . . .

33 Pseudocode for ricochet
void ricochet(Car& moving_car); The recursive call will solve the smaller problem. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: Still in that recursive call! moving_car.move( ); ricochet(moving_car); . . .

34 Pseudocode for ricochet
void ricochet(Car& moving_car); The recursive call will solve the smaller problem. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: The work of the recursive call has taken the car to the barrier... moving_car.move( ); ricochet(moving_car); . . .

35 Pseudocode for ricochet
void ricochet(Car& moving_car); The recursive call will solve the smaller problem. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: ...and turned the car around. moving_car.move( ); ricochet(moving_car); . . .

36 Pseudocode for ricochet
void ricochet(Car& moving_car); The recursive call will solve the smaller problem. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: The recursive call is still working to solve the smaller problem... moving_car.move( ); ricochet(moving_car); . . .

37 Pseudocode for ricochet
void ricochet(Car& moving_car); The recursive call will solve the smaller problem. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: ...eventually the recursive call will bring the car back... moving_car.move( ); ricochet(moving_car); . . .

38 Pseudocode for ricochet
void ricochet(Car& moving_car); The recursive call will solve the smaller problem. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: ...to the position where the recursive call was made. moving_car.move( ); ricochet(moving_car); . . .

39 Pseudocode for ricochet
void ricochet(Car& moving_car); The recursive call will solve the smaller problem. if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: At last! The recursive call has finished its work, solving the smaller problem. In the solution of the smaller problem, the recursive call moved the car forward to the barrier, turned the car around, and moved the car back to the starting position of the smaller problem, but facing the opposite direction Another key point: You don't need to know all the details of how that recursive call worked. Just have confidence that the recursive call will solve the smaller problem. You can safely have this confidence provided that: 1. When the problem gets small enough, there won't be more recursive calls, and 2. Each recursive call makes the problem "smaller". moving_car.move( ); ricochet(moving_car); . . . 99 ft.

40 Pseudocode for ricochet
void ricochet(Car& moving_car); What is the last step that's needed to return to our original location ? if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: After the recursive call solves the smaller problem, there is one more step that must be taken to solve the original problem. Question: What is that remaining step? moving_car.move( ); ricochet(moving_car); . . . 99 ft.

41 Pseudocode for ricochet
void ricochet(Car& moving_car); What is the last step that's needed to return to our original location ? if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: Answer: The car must be moved one more step to bring it back to its original position. moving_car.move( ); ricochet(moving_car); 100 ft.

42 Pseudocode for ricochet
void ricochet(Car& moving_car); if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: This recursive function follows a common pattern that you should recognize. The pattern followed by this recursive function is a common one that you should be able to recognize and implement for similar problems. moving_car.move( ); ricochet(moving_car);

43 Pseudocode for ricochet
void ricochet(Car& moving_car); if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: When the problem is simple, solve it with no recursive call. This is the base case or the stopping case. The start of the pattern is called the base case. This is the case where the problem is so simple that it can be solved with no recursion. Question: What do you suppose would happen if we forgot to include a base case? Answer: In theory, the recursion would never end. Each recursive call would make another recursive call which would make another recursive call, and so on forever. In practice, each recursive call uses some of the computer's memory, and eventually you will run out of memory. At that point, a run-time error occurs stating "Run-time stack overflow" or perhaps "Stack-Heap collision." Both messages mean that you have run out of memory to make function calls. moving_car.move( ); ricochet(moving_car);

44 Pseudocode for ricochet
void ricochet(Car& moving_car); if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: When the problem is more complex, start by doing work to create a smaller version of the same problem... If the base case does not apply, then the algorithm will work by creating a smaller version of the original problem. moving_car.move( ); ricochet(moving_car);

45 Pseudocode for ricochet
void ricochet(Car& moving_car); if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: ...use a recursive call to completely solve the smaller problem... The smaller problem is solved with a recursive call. moving_car.move( ); ricochet(moving_car);

46 Pseudocode for ricochet
void ricochet(Car& moving_car); if moving_car.is_blocked( ), then the car is already at the barrier. In this case, just turn the car around. Otherwise, the car has not yet reached the barrier, so start with: ...and finally do any work that's needed to complete the solution of the original problem.. Generally a bit of extra work is done before the recursive call, in order to create the smaller problem. Work is often needed after the recursive call too, to finish solving the larger problem. Programmer's Hint: If you find that no work is needed after the recursive call, then you have a simple kind of recursion called "tail recursion". The word "tail" refers to the fact that the recursive call is the final thing the algorithm does. Tail recursion is nearly always a bad idea. The reason is that tail recursive algorithms can generally be written much simpler with a loop and no recursive call. moving_car.move( ); ricochet(moving_car);

47 Implementation of ricochet
void ricochet(Car& moving_car) { if (moving_car.is_blocked( )) moving_car.turn_around( ); // Base case else { // Recursive pattern moving_car.move( ); ricochet(moving_car); } Look for this pattern in the other examples of Chapter 9. You'll find this same pattern in other problems of Chapter 9, such as the recursive functions to write the digits of a number vertically.

48 An Exercise Can you write ricochet as a new member function of the Car class, instead of a separate function? void Car::ricochet( ) { . . . Time for another quiz . . . Turn in in 5 minutes You have 2 minutes to write the implementation.

49 An Exercise One solution: void Car::ricochet( ) { if (is_blocked( ))
turn_around( ); // Base case else { // Recursive pattern move( ); ricochet( ); } Two key points: 1. The recursive member function does not need a parameter, since it must be activated by a particular car. For example, if you have a car named racer, then you can activate racer's ricochet function with: racer.ricochet( ); 2. The recursive member function activates itself. Do you see where this happens?

50 Recursive Thinking: General Form
Recursive Calls Suppose a problem has one or more cases in which some of the subtasks are simpler versions of the original problem. These subtasks can be solved by recursive calls Stopping Cases /Base Cases A function that makes recursive calls must have one or more cases in which the entire computation is fulfilled without recursion. These cases are called stopping cases or base cases

51 Tracing Recursive Calls: Ricochet
Do it by hand if car is 4 feet away from the barrier void Car::ricochet( ) { if (is_blocked( )) A turn_around( ); // Base case else { // Recursive pattern B move( ); C ricochet( ); D move( ); E } } Two key points: (1) The recursive case and the stopping case (2) Activation record The return location only in this example – other information (the barrier) will be detected by the sensor on the car) (3) The running stack The collection of the activation records is stored in a stack data structure In the demo only the record of the recursive call is tracked

52 A Close Look at Ricochet Recursion
The recursive case and the stopping case Activation record The return location only in this example – other information is kept in the object racer The running stack The collection of the activation records is stored in a stack data structure

53 Example 2: Write Number Vertically
Task Write a non-negative integer to the screen with its decimal digits stacked vertically for example: Input 1234 Output: 1 2 3 4 Problem statement How can we write a program to do this? if 1234 is divided by 1000, then the quotient will be 1234/1000 = 1; if (1234-1*1000) = 234 is divided by 100, then the quotient will be 234/100 = 2; (234-2*100) = 34; 34/10 = 3; (34-3*10) = 4; How about 12345? It is much easier to do it in a reversed order n%10 ->the last digit n <- n/10; all the digit except the last

54 A possible function Write an integer number vertically
void write_vertical (unsigned int number) // precondition: number >=0 // Postcondition: The digits of number have been written, stacked vertically. { assert(number>=0); do { cout << number % 10 << endl; // Write a digit number = number / 10; } while (number !=0); } First let’s use a simple while loop to try to solve this problem. We know how to get the last digit of a number by using the modulo function number%10 – this is the remainder upon dividing number by 10. Then we get the quotient by dividing the number by print the remainder until the number is zero. However the order of the digits is not want we desired – we want the number stacked in the reverse order – so the first approach is to use a stack to save the digits first, and then print the digits from the stack Input 1234 Output: 4 3 2 1

55 Approach 1: using a stack
Write an integer number vertically void stack_write_vertical (unsigned int number) // Postcondition: The digits of number have been written, stacked vertically. { stack<int> s; do s.push(number % 10); // push a digit in the stack number = number / 10; } while (number !=0); while (!(s.empty())) cout << s.top()<< endl; //print a digit from the stack s.pop(); } Using a stack, we need at least two while loops – in the first while loop we push the digits in a stack; in the second one, we pop the digits from the stack after printing out the top. Can we accomplish our ask by a simpler coding – yes, using recursion

56 Approach 2: Using Recursion
Write an integer number vertically void recursive_write_vertical(unsigned int number) // Postcondition: The digits of number have been written, stacked vertically. { if (number < 10) // stopping case cout << number << endl; // Write the one digit else // including recursive calls recursive_write_vertical(number/10); // Write all but the last digit cout << number % 10 << endl; // Write the last digit } Two cases: If the number only has one digit we simply print it out Otherwise we break the original task into two subtasks: First print all except the last digit, stacked vertically Then print the last digit The first subtask is a simpler instance of the same task of writing a number’s digits vertically. It’s simpler because number/10 has fewer digits This step can be implemented by making a recursive call of the original function

57 Tracing Recursive Calls
Write an integer number vertically void recursive_write_vertical_2(unsigned int number) // Postcondition: The digits of number have been written, stacked vertically. { if (number < 10) // stopping case A cout << number << endl; // Write the one digit else // including recursive calls B recursive_write_vertical(number/10); // Write all but the last digit C cout << number % 10 << endl; // Write the last digit D } } Two key points: (1) The recursive case and the stopping case (2) Activation record The return location and one formal parameter of the calling function when a function is called – a recursive one or ordinary one (3) The running stack The collection of the activation records is stored in a stack data structure

58 A Closer Look at the Recursion
Recursive Function Recursive calls Stopping (Base) cases Run-time Stack the collection of activation records is stored in the stack Activation Record - a special memory block including return location of a function call values of the formal parameters and local variables

59 Recursive Thinking: General Form
Recursive Calls Suppose a problem has one or more cases in which some of the subtasks are simpler versions of the original problem. These subtasks can be solved by recursive calls Stopping Cases /Base Cases A function that makes recursive calls must have one or more cases in which the entire computation is fulfilled without recursion. These cases are called stopping cases or base cases

60 Self-Tests and More Complicated Examples
An Extension of write_vertical (page 436) handles all integers including negative ones Hints: you can have more than one recursive calls or stopping cases in your recursive function Homework Reading: Section 9.1 Self-Test: Exercises 1-8 Advanced Reading: Section 9.2 Assignment 5 online

61 super_write_vertical
Write any integer number vertically void super_write_vertical(int number) // Postcondition: The digits of the number have been written, stacked vertically. // If number is negative, then a negative sign appears on top. // Library facilities used: iostream.h, math.h { if (number < 0) cout << '-' << endl; // print a negative sign super_write_vertical(abs(number)); // abs computes absolute value // This is Spot #1 referred to in the text. } else if (number < 10) cout << number << endl; // Write the one digit else super_write_vertical(number/10); // Write all but the last digit // This is Spot #2 referred to in the text. cout << number % 10 << endl; // Write the last digit


Download ppt "Lecture 11 Recursive Thinking CSC212 Data Structure"

Similar presentations


Ads by Google