Download presentation
Presentation is loading. Please wait.
1
Recursion Eric Roberts CS 106B
2
Part I: Recursive Strategies
3
Recursion One of the most important “Great Ideas” in this course is the concept of recursion, which is the process of solving a problem by dividing it into smaller subproblems of the same form. The italicized phrase is the essential characteristic of recursion. The fact that recursive decomposition generates subproblems that have the same form as the original problem means that recursive programs will use the same function or method to solve subproblems at different levels of the solution. In terms of the structure of the code, the defining characteristic of recursion is having functions that call themselves, directly or indirectly, as the decomposition process proceeds.
4
A Simple Illustration of Recursion
Suppose that you are the national fundraising director for a charitable organization and need to raise $1,000,000. One possible approach is to find a wealthy donor and ask for a single $1,000,000 contribution. The problem with that strategy is that individuals with the necessary combination of means and generosity are difficult to find. Donors are much more likely to make contributions in the $100 range. Another strategy would be to ask 10,000 friends for $100 each. Unfortunately, most of us don’t have 10,000 friends. There are, however, more promising strategies. You could, for example, find ten regional coordinators and charge each one with raising $100,000. Those regional coordinators could in turn delegate the task to local coordinators, each with a goal of $10,000, continuing the process reached a manageable contribution level.
5
A Simple Illustration of Recursion
The following diagram illustrates the recursive strategy for raising $1,000,000 described on the previous slide: Goal: $1,000,000 Goal: $100,000 Goal: $10,000 Goal: $1000 Goal: $100
6
A Pseudocode Fundraising Strategy
If you were to implement the fundraising strategy in the form of a C++ function, it would look something like this: void collectContributions(int n) { if (n <= 100) { Collect the money from a single donor. } else { Find 10 volunteers. Get each volunteer to collect n/10 dollars. Combine the money raised by the volunteers. } What makes this strategy recursive is that the line Get each volunteer to collect n/10 dollars. will be implemented using the following recursive call: collectContributions(n / 10);
7
The Towers of Hanoi In the great temple at Benares beneath the dome which marks the center of the world, rests a brass plate in which are fixed three diamond needles, each a cubit high and as thick as the body of a bee. On one of these needles, at the creation, God placed sixty-four disks of pure gold, the largest disk resting on the brass plate and the others getting smaller and smaller up to the top one. This is the Tower of Brahma. Day and night unceasingly, the priests transfer the disks from one diamond needle to another according to the fixed and immutable laws of Brahma, which require that the priest on duty must not move more than one disk at a time and that he must place this disk on a needle so that there is no smaller disk below it. When all the sixty-four disks shall have been thus transferred from the needle on which at the creation God placed them to one of the other needles, tower, temple and Brahmins alike will crumble into dust, and with a thunderclap the world will vanish. —Henri de Parville, La Nature, 1883
8
The Towers of Hanoi Solution
int main() { int nDisks = 3; initHanoiGraphics(nDisks); moveTower(nDisks, 'A', 'B', 'C'); return 0; } void moveTower(int n, char start, char finish, char temp) { if (n == 1) { moveSingleDisk(start, finish); } else { moveTower(n - 1, start, temp, finish); moveTower(n - 1, temp, finish, start); start finish 'A' 'B' n 3 temp 'C' int main() { int nDisks = 3; initHanoiGraphics(nDisks); moveTower(nDisks, 'A', 'B', 'C'); return 0; } void moveTower(int n, char start, char finish, char temp) { if (n == 1) { moveSingleDisk(start, finish); } else { moveTower(n - 1, start, temp, finish); moveTower(n - 1, temp, finish, start); } start finish 'A' 'B' n 3 temp 'C' void moveTower(int n, char start, char finish, char temp) { if (n == 1) { moveSingleDisk(start, finish); } else { moveTower(n - 1, start, temp, finish); moveTower(n - 1, temp, finish, start); } start finish 'C' 'B' n 2 temp 'A' void moveTower(int n, char start, char finish, char temp) { if (n == 1) { moveSingleDisk(start, finish); } else { moveTower(n - 1, start, temp, finish); moveTower(n - 1, temp, finish, start); } start finish 'A' 'C' n 2 temp 'B' void moveTower(int n, char start, char finish, char temp) { if (n == 1) { moveSingleDisk(start, finish); } else { moveTower(n - 1, start, temp, finish); moveTower(n - 1, temp, finish, start); } start finish 'C' 'A' n 1 temp 'B' void moveTower(int n, char start, char finish, char temp) { if (n == 1) { moveSingleDisk(start, finish); } else { moveTower(n - 1, start, temp, finish); moveTower(n - 1, temp, finish, start); } start finish 'A' 'B' n 1 temp 'C' void moveTower(int n, char start, char finish, char temp) { if (n == 1) { moveSingleDisk(start, finish); } else { moveTower(n - 1, start, temp, finish); moveTower(n - 1, temp, finish, start); } start finish 'B' 'C' n 1 temp 'A' void moveTower(int n, char start, char finish, char temp) { if (n == 1) { moveSingleDisk(start, finish); } else { moveTower(n - 1, start, temp, finish); moveTower(n - 1, temp, finish, start); } start finish 'A' 'B' n 1 temp 'C' nDisks 3 Hanoi skip simulation
9
The Recursive “Leap of Faith”
The purpose of going through the complete decomposition of the Towers of Hanoi problem is to convince you that the process works and that recursive calls are in fact no different from other method calls, at least in their internal operation. The danger with going through these details is that it might encourage you to do the same when you write your own recursive programs. As it happens, tracing through the details of a recursive program almost always makes such programs harder to write. Writing recursive programs becomes natural only after you have enough confidence in the process that you don’t need to trace them fully. As you write a recursive program, it is important to believe that any recursive call will return the correct answer as long as the arguments define a simpler subproblem. Believing that to be true—even before you have completed the code—is called the recursive leap of faith.
10
The Recursive Paradigm
Most recursive functions you encounter in an introductory course have bodies that fit the following general pattern: if (test for a simple case) { Compute and return the simple solution without using recursion. } else { Divide the problem into one or more subproblems that have the same form. Solve each of the problems by calling this method recursively. Return the solution from the results of the various subproblems. } Finding a recursive solution is mostly a matter of figuring out how to break it down so that it fits the paradigm. When you do so, you must do two things: Identify simple cases that can be solved without recursion. 1. Find a recursive decomposition that breaks each instance of the problem into simpler subproblems of the same type, which you can then solve by applying the method recursively. 2.
11
Generating Mondrian-Style Paintings
Fig. 11: Three real Mondrian paintings, and three samples from our targeting function. Can you tell which is which? Source: Jerry O. Talton, Yu Lou, Steve Lesser, Jared Duke, Radomír Měch, and Vladlen Koltun, “Metropolis Procedural Modeling,” ACM Transactions on Graphics, April 2011.
12
Mondrian Decomposition
13
Methods in the Graphics Library
initGraphics(width, height) Creates a graphics window with the specified dimensions. drawLine(x0, y0, x1, y1) Draws a line connecting the points (x0, y0) and (x1, y1). drawPolarLine(x0, y0, r, theta) Draws a line r pixels long in direction theta from (x0, y0). To make chaining line segments easier, this function returns the ending coordinates as a GPoint. getWindowWidth() Returns the width of the graphics window. getWindowHeight() Returns the height of the graphics window. Many more functions exist in the graphics.h interface, which is described on the web site.
14
Code for the Mondrian Program
#include <iostream> #include "graphics.h" #include "random.h" using namespace std; /* Constants */ const double MIN_AREA = 10000; /* Smallest square that will be split */ const double MIN_EDGE = 20; /* Smallest edge length allowed */ /* Function prototypes */ void subdivideCanvas(double x, double y, double width, double height); /* Main program */ int main() { initGraphics(); subdivideCanvas(0, 0, getWindowWidth(), getWindowHeight()); return 0; } Page 1 of 2
15
Code for the Mondrian Program
void subdivideCanvas(double x, double y, double width, double height) { if (width * height >= MIN_AREA) { if (width > height) { double mid = randomReal(MIN_EDGE, width - MIN_EDGE); subdivideCanvas(x, y, mid, height); subdivideCanvas(x + mid, y, width - mid, height); drawLine(x + mid, y, x + mid, y + height); } else { double mid = randomReal(MIN_EDGE, height - MIN_EDGE); subdivideCanvas(x, y, width, mid); subdivideCanvas(x, y + mid, width, height - mid); drawLine(x, y + mid, x + width, y + mid); } #include <iostream> #include "graphics.h" #include "random.h" using namespace std; /* Constants */ const double MIN_AREA = 10000; /* Smallest square that will be split */ const double MIN_EDGE = 20; /* Smallest edge length allowed */ /* Function prototypes */ void subdivideCanvas(double x, double y, double width, double height); /* Main program */ int main() { initGraphics(); subdivideCanvas(0, 0, getWindowWidth(), getWindowHeight()); return 0; } Page 2 of 2
16
Exercise: A Better Mondrian Program
Can you do a better job of emulating Mondrian’s style? Suppose that you have the following additional functions: drawRect(x, y, width, height) Draws the outline of a rectangle with the specified bounds. fillRect(x, y, width, height) Fills the outline of the specified rectangle using the current color. setColor(color) Sets the pen color to the specified color string (such as "BLACK" or "RED") setColor("#rrggbb") Sets the red/green/blue components to the specified hexadecimal values.
17
Revised Mondrian Decomposition
18
Recursion and Fractals
Recursion comes up in other graphical applications, most notably in the creation of fractals, which are mathematical structures consisting of similar figures at various different scales. Fractals were popularized in a 1982 book by the late Benoit Mandelbrot entitled The Fractal Geometry of Nature. One of the simplest fractal patterns to draw is the Koch fractal, named after its inventor, the Swedish mathematician Helge von Koch ( ). The Koch fractal is sometimes called a snowflake fractal because of the beautiful, six-sided symmetries it displays as the figure becomes more detailed. as illustrated in the following diagram:
19
How Long is the Coast of England?
50km ruler 100km ruler 200km ruler The first widely circulated paper about fractals was a 1967 article in Science by Mandelbrot that asked the seemingly innocuous question, “How long is the coast of England?” The point that Mandelbrot made in the article is that the answer depends on the measurement scale, as these images from Wikipedia show. This thought-experiment serves to illustrate the fact that coastlines are fractal in that they exhibit the same structure at every level of detail.
20
Exercise: Fractal Coastline
Exercise 15 on page 386 asks you to draw a fractal coastline between two points, A and B, on the graphics window. The order-0 coastline is just a straight line. The order-1 coastline replaces that line with one containing a triangular wedge pointing randomly up or down. The order-2 coastline does the same for each line in the order-1. Repeating this process eventually yields an order-5 coastline. Coastline
21
Part II: Recursive Backtracking
22
Solving a Maze A journey of a thousand miles begins with a single step. —Lao Tzu, 6th century B.C.E. The example most often used to illustrate recursive backtracking is the problem of solving a maze, which has a long history in its own right. The most famous maze in history is the labyrinth of Daedalus in Greek mythology where Theseus slays the Minotaur. There are passing references to this story in Homer, but the best known account comes from Ovid in Metamorphoses. . . . When Minos, willing to conceal the shame That sprung from the reports of tatling Fame, Resolves a dark inclosure to provide, And, far from sight, the two-form’d creature hide. Great Daedalus of Athens was the man That made the draught, and form’d the wondrous plan; Where rooms within themselves encircled lye, With various windings, to deceive the eye Such was the work, so intricate the place, That scarce the workman all its turns cou’d trace; And Daedalus was puzzled how to find The secret ways of what himself design’d. These private walls the Minotaur include, Who twice was glutted with Athenian blood: But the third tribute more successful prov’d, Slew the foul monster, and the plague remov’d. When Theseus, aided by the virgin’s art, Had trac’d the guiding thread thro’ ev’ry part, He took the gentle maid, that set him free, And, bound for Dias, cut the briny sea. There, quickly cloy’d, ungrateful, and unkind, Left his fair consort in the isle behind . . . Metamorphoses —Ovid, 1 A.C.E.
23
The Right-Hand Rule The most widely known strategy for solving a maze is called the right-hand rule, in which you put your right hand on the wall and keep it there until you find an exit. If Theseus applies the right-hand rule in this maze, the solution path looks like this. Unfortunately, the right-hand rule doesn’t work if there are loops in the maze that surround either the starting position or the goal. In this maze, the right-hand rule sends Theseus into an infinite loop.
24
A Recursive View of Mazes
It is also possible to solve a maze recursively. Before you can do so, however, you have to find the right recursive insight. Consider the maze shown at the right. How can Theseus transform the problem into one of solving a simpler maze? The insight you need is that a maze is solvable only if it is possible to solve one of the simpler mazes that results from shifting the starting location to an adjacent square and taking the current square out of the maze completely.
25
A Recursive View of Mazes
Thus, the original maze is solvable only if one of the three mazes at the bottom of this slide is solvable. Each of these mazes is “simpler” because it contains fewer squares. The simple cases are: Theseus is outside the maze There are no directions left to try
26
The Maze Class /* * Class: Maze * -----------
* This class represents a two-dimensional maze contained in a rectangular * grid of squares. The maze is read in from a data file in which the * characters '+', '-', and '|' represent corners, horizontal walls, and * vertical walls, respectively; spaces represent open passageway squares. * The starting position is indicated by the character 'S'. For example, * the following data file defines a simple maze: * * * | | * * |S | | */ class Maze { public:
27
The Maze Class /* * Constructor: Maze * Usage: Maze maze(filename);
* * Constructs a new maze by reading the specified data file. */ Maze(string filename); * Method: getStartPosition * Usage: Point start = maze.getStartPosition(); * * Returns a Point indicating the coordinates of the start square. Point getStartPosition(); * Method: isOutside * Usage: if (maze.isOutside(start)) . . . * * Returns true if the specified point is outside the boundary of the maze. bool isOutside(Point start); /* * Class: Maze * * This class represents a two-dimensional maze contained in a rectangular * grid of squares. The maze is read in from a data file in which the * characters '+', '-', and '|' represent corners, horizontal walls, and * vertical walls, respectively; spaces represent open passageway squares. * The starting position is indicated by the character 'S'. For example, * the following data file defines a simple maze: * * * | | * * |S | | */ class Maze { public:
28
The Maze Class /* * Method: wallExists
* Usage: if (maze.wallExists(start, dir)) . . . * * Returns true if there is a wall in direction dir from the square at start. */ bool wallExists(Point start, Direction dir); * Method: markSquare * Usage: maze.markSquare(start); * * Marks the specified square in the maze. void markSquare(Point start); * Method: unmarkSquare * Usage: maze.unmarkSquare(start); * * Unmarks the specified square in the maze. void unmarkSquare(Point start); /* * Constructor: Maze * Usage: Maze maze(filename); * * Constructs a new maze by reading the specified data file. */ Maze(string filename); * Method: getStartPosition * Usage: Point start = maze.getStartPosition(); * * Returns a Point indicating the coordinates of the start square. Point getStartPosition(); * Method: isOutside * Usage: if (maze.isOutside(start)) . . . * * Returns true if the specified point is outside the boundary of the maze. bool isOutside(Point start);
29
The Maze Class /* * Method: isMarked
* Usage: if (maze.isMarked(start)) . . . * * Returns true if the specified square is marked. */ bool isMarked(Point start); * Method: showInGraphicsWindow * Usage: showInGraphicsWindow(); * showInGraphicsWindow(x, y); * * Displays the maze in the graphics window, positioned with its origin * at the point (x, y), which defaults to center the maze in the window. void showInGraphicsWindow(); void showInGraphicsWindow(double x, double y); #include "mazepriv.h" }; /* * Method: wallExists * Usage: if (maze.wallExists(start, dir)) . . . * * Returns true if there is a wall in direction dir from the square at start. */ bool wallExists(Point start, Direction dir); * Method: markSquare * Usage: maze.markSquare(start); * * Marks the specified square in the maze. void markSquare(Point start); * Method: unmarkSquare * Usage: maze.unmarkSquare(start); * * Unmarks the specified square in the maze. void unmarkSquare(Point start);
30
The solveMaze Function
/* * Function: solveMaze * Usage: solveMaze(maze, start); * * Attempts to generate a solution to the current maze from the specified * start point. The solveMaze function returns true if the maze has a * solution and false otherwise. The implementation uses recursion * to solve the submazes that result from marking the current square * and moving one step along each open passage. */ bool solveMaze(Maze & maze, Point start) { if (maze.isOutside(start)) return true; if (maze.isMarked(start)) return false; maze.markSquare(start); for (Direction dir = NORTH; dir <= WEST; dir++) { if (!maze.wallExists(start, dir)) { if (solveMaze(maze, adjacentPoint(start, dir))) { return true; } maze.unmarkSquare(start); return false;
31
Tracing the solveMaze Function
bool solveMaze(Maze & maze, Point start) { if (maze.isOutside(start)) return true; if (maze.isMarked(start)) return false; maze.markSquare(start); for (int i = 0; i < 4; i++) { Direction dir = Direction(i); if (!maze.wallExists(start, dir)) { if (solveMaze(maze, AdjPt(start, dir))) { return true; } maze.unmarkSquare(start); return false; x x x x x x x bool solveMaze(Maze & maze, Point start) { if (maze.isOutside(start)) return true; if (maze.isMarked(start)) return false; maze.markSquare(start); for (int i = 0; i < 4; i++) { Direction dir = Direction(i); if (!maze.wallExists(start, dir)) { if (solveMaze(maze, AdjPt(start, dir))) { return true; } maze.unmarkSquare(start); return false; dir i start (3, 4) bool solveMaze(Maze & maze, Point start) { if (maze.isOutside(start)) return true; if (maze.isMarked(start)) return false; maze.markSquare(start); for (int i = 0; i < 4; i++) { Direction dir = Direction(i); if (!maze.wallExists(start, dir)) { if (solveMaze(maze, AdjPt(start, dir))) { return true; } maze.unmarkSquare(start); return false; dir i start (3, 2) x x x x x x x x x x x x x x x x x x x x x x x x x x x x start i dir (3, 3) 1 2 SOUTH NORTH EAST Don’t follow the recursion more than one level. Depend on the recursive leap of faith.
32
Reflections on the Maze Problem
The solveMaze program is a useful example of how to search all paths that stem from a branching series of choices. At each square, the solveMaze program calls itself recursively to find a solution from one step further along the path. To give yourself a better sense of why recursion is important in this problem, think for a minute or two about what it buys you and why it would be difficult to solve this problem iteratively. In particular, how would you answer the following questions: What information does the algorithm need to remember as it proceeds with the solution, particularly about the options it has already tried? In the recursive solution, where is this information kept? How might you keep track of this information otherwise?
33
Consider a Specific Example
Suppose that the program has reached the following position: x How does the algorithm keep track of the “big picture” of what paths it still needs to explore?
34
Each Frame Remembers One Choice
bool solveMaze(Maze & maze, Point start) { if (maze.isOutside(start)) return true; if (maze.isMarked(start)) return false; maze.markSquare(start); for (int i = 0; i < 4; i++) { Direction dir = Direction(i); if (!maze.wallExists(start, dir)) { if (solveMaze(maze, AdjPt(start, dir))) { return true; } maze.unmarkSquare(start); return false; dir i start (3, 3) 2 SOUTH x bool solveMaze(Maze & maze, Point start) { if (maze.isOutside(start)) return true; if (maze.isMarked(start)) return false; maze.markSquare(start); for (int i = 0; i < 4; i++) { Direction dir = Direction(i); if (!maze.wallExists(start, dir)) { if (solveMaze(maze, AdjPt(start, dir))) { return true; } maze.unmarkSquare(start); return false; dir i start (3, 4) 1 EAST x bool solveMaze(Maze & maze, Point start) { if (maze.isOutside(start)) return true; if (maze.isMarked(start)) return false; maze.markSquare(start); for (int i = 0; i < 4; i++) { Direction dir = Direction(i); if (!maze.wallExists(start, dir)) { if (solveMaze(maze, AdjPt(start, dir))) { return true; } maze.unmarkSquare(start); return false; dir i start (4, 4) 1 EAST x bool solveMaze(Maze & maze, Point start) { if (maze.isOutside(start)) return true; if (maze.isMarked(start)) return false; maze.markSquare(start); for (int i = 0; i < 4; i++) { Direction dir = Direction(i); if (!maze.wallExists(start, dir)) { if (solveMaze(maze, AdjPt(start, dir))) { return true; } maze.unmarkSquare(start); return false; dir i start (3) NORTH x bool solveMaze(Maze & maze, Point start) { if (maze.isOutside(start)) return true; if (maze.isMarked(start)) return false; maze.markSquare(start); for (int i = 0; i < 4; i++) { Direction dir = Direction(i); if (!maze.wallExists(start, dir)) { if (solveMaze(maze, AdjPt(start, dir))) { return true; } maze.unmarkSquare(start); return false; dir i start (5, 3) 3 WEST x bool solveMaze(Maze & maze, Point start) { if (maze.isOutside(start)) return true; if (maze.isMarked(start)) return false; maze.markSquare(start); for (int i = 0; i < 4; i++) { Direction dir = Direction(i); if (!maze.wallExists(start, dir)) { if (solveMaze(maze, AdjPt(start, dir))) { return true; } maze.unmarkSquare(start); return false; dir i start (4, 3) x
35
Recursion and Concurrency
The recursive decomposition of a maze generates a series of independent submazes; the goal is to solve any one of them. If you had a multiprocessor computer, you could try to solve each of these submazes in parallel. This strategy is analogous to cloning yourself at each intersection and sending one clone down each path. 6 5 5 5 5 4 4 6 5 1 1 5 5 4 7 5 1 1 2 5 4 7 3 3 1 2 2 4 7 3 3 2 2 2 4 7 7 3 3 3 4 4 8 7 7 7 3 3 3 7 Is this parallel strategy more efficient?
36
The P = NP Question The question of whether a parallel solution is fundamentally faster than a sequential one is related to the biggest open problem in computer science, for which there is a $1M prize.
37
Exercise: Keeping Track of the Path
As described in exercise 3 on page 420, it is possible to build a better version of solveMaze so that it keeps track of the solution path as the computation proceeds. Write a new function bool findPath(Maze & maze, Point start, Vector<Point> & path); that records the solution path in a vector of Point values passed as a reference parameter. The findPath function should also return a Boolean value indicating whether the maze is solvable, just as solveMaze does.
38
Reference Basic Advanced 数据结构(C++语言版) Chapter 1.4
Programming Abstractions in C++ Chapter 8 Advanced Data Structures and Algorithm Analysis in C++ Chapter 1.3
39
Next Advanced Recursion
40
The End
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.