64
19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions are defined recursively, and redefining them in a non- recursive manner is NOT obvious. Many problems can have their solution described recursively in an easy to understand manner, while a non-recursive solution can be described only with difficulty.

19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

Embed Size (px)

Citation preview

Page 1: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Recursion:

How to solve a problem by doing some work and sending the rest out to be handled the same way.

Many mathematical functions are defined recursively, and redefining them in a non-recursive manner is NOT obvious.

Many problems can have their solution described recursively in an easy to understand manner, while a non-recursive solution can be described only with difficulty.

Page 2: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Recursion: some misconceptions.

It’s hard to understand.

NO, it’s not - but it may require some practice. Like many unfamiliar ways of doing something, it will require a bit of work to become “easy”.

It’s inefficient.

NO, it’s not. First of all, any description of a process that makes it EASIER for the human to understand and code correctly is to be preferred to a harder one. Second, efficiency depends (usually) more on choice of algorithm and compiler technology than on “twiddling bits”.

Page 3: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Summing Integers:

∑n = 0

m n = ??

0 + 1 + 2 + … + (m - 1) + m = ??

The standard formula for this sum is

(∑n = 0

m n) = m (m + 1) / 2,

So nobody in his right mind would actually SUM the things one at a time, since a quick formula exists. Similar formulae are known for the sums of many low powers.

Page 4: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Thinking Recursively:

split the solution into three parts:

a) do some work towards solving the problem;

b) apply the method to one (or more) smaller problem(s) on one (or more) proper subset(s) of the set of data;

c) glue the work done in a) and b) together.

Page 5: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Example:

go(bed, school):

from bed to bedroom_door

+ go(bedroom_door, school);

go(bedroom_door, school):

from bedroom_door to house_door

+ go(house_door, school);

etc…

go(school, school):

just return; (i.e., you’re done - do nothing else, or indicate success)

Page 6: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Apply this idea to the problem of summing the integers:

int SumInts(int low, int high)

{ /* first check the BASE CASE */

/* safety… don’t let low exceed high EVER */

if (low >= high) // done - return the last value

return(high);

else // this is where we do some work

return(low + SumInts(low + 1, high));

}

Page 7: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

The “termination condition”, although safe, does NOT quite return the correct value if we input n < m. A better solution (although a little less obvious) would be:

int SumInts(int m, int n)

{

/* first check the BASE CASE */

if (m > n) /* safety… m has exceeded n */

return(0); /* overshot: no ints to add */

else /* do some work */

return(m + SumInts(m + 1, n));

}

We need to check this is correct: verify it!!

Page 8: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Let’s make a full program out of it:#include <stdio.h>long low, high;long SumInts(long m, long n){ /* first check the BASE CASE */ if (m > n) return(0);// overshot: no ints to add else return(m + SumInts(m + 1, n)); // do the work}int main(void){

printf("Enter a non-negative integer : ");scanf("%d", &low);printf("Enter another non-negative integer: ");scanf("%d", &high);printf("The sum is: %d.\n", SumInts(low, high));

}

Page 9: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Aside: The idea of a “stack frame”.

Each time a function is called, the system needs to save some information, in particular:

The values of the parameters passed;

The local variable declared in the body;

What the function is returning (return value);

Where the function is returning (return address);

The memory allocated to hold this information is called a “stack frame”. These frames are allocated by the system when a function is called and are deallocated when the function terminates.

Page 10: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

MainProc A Proc D

B CProc D

Proc D Proc D

Time

Space

E

Page 11: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Recursive function calls could result in a large number of such memory chunks being in use at one time. This may cause problems, especially in situations when memory is at a premium.

Rudimentary compilers are unable to optimize code in such a way that such frames will be allocated only when necessary; more sophisticated compilers do perform such optimizations.

Check your compiler…

End of aside.

Page 12: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

There are many ways of “solving a problem”: we saw how to add integers “going up” - essentially adding one integer at a time.

We can solve the same problem by adding “going down”, i.e. starting from the “upper” end of the set {m, m + 1, …, n - 1, n}

int SumInts(int m, int n)

{ /* first check the BASE CASE */

if (n < m) // safety… m has exceeded n

return(0); // overshot

else // do some work

return(n + SumInts(m, n - 1));

}

Page 13: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

There is a method called “divide and conquer”: it splits the work into two roughly equal parts and glues the results:int SumInts(int m, int n)

{ int mid;

/* first check the BASE CASE */

if (n < m) return(0); // overshot

else // do some work

{ mid = (m + n)/2; // compute midpoint

return(SumInts(m, mid - 1)

+ mid

+ SumInts(mid + 1, n));

}

}

Page 14: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

What is wrong with this?

int SumInts(int m, int n)

{ int mid;

/* first check the BASE CASE */

if (n < m) // overshot

return(0);

else { // do some work

mid = (m + n)/2; // compute midpoint

return(SumInts(m, mid) // return result

+ SumInts(mid + 1, n));

}

}

Page 15: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Solution:

int SumInts(int m, int n)

{ int mid;

/* first check the BASE CASE */

if (n < m) return(0); // overshot

else if (n == m) return(n); // endpoint

else // do the work

{ mid = (m + n)/2; // compute midpoint

return(SumInts(m, mid)

+ SumInts(mid + 1, n));

}

}

Page 16: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

How can we visualize the behavior of a recursive algorithm?

int SumInts(int low, int high)

{ /* first check the BASE CASE */

if (low >= high)

return(high);

else

return(low + SumInts(low + 1, high));

}

Try a SPECIFIC case: SumInts(0, 5).

Page 17: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

SumInts(0, 5)

= 0 + SumInts(1, 5)

= 0 + (1 + SumInts(2, 5))

= 0 + (1 + (2 + SumInts(3, 5)))

= 0 + (1 + (2 + (3 + SumInts(4, 5))))

= 0 + (1 + (2 + (3 + (4 + SumInts(5, 5)))))

= 0 + (1 + (2 + (3 + (4 + 5))))

= 0 + (1 + (2 + (3 + 9)))

= 0 + (1 + (2 + 12))

= 0 + (1 + 14)

= 0 + 15 = 15 = (5*6)/2 // from the formula

Page 18: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

0 5 0+

1 5

3+

4+54

5

1+

3

2 5 2+

55

4+53+92+121+140+15

15

Page 19: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Code for “visualization”. How can we change at least some of our recursive procedures so that the shape of the recursion becomes visible? We could begin by adding a couple of printf statements:

int SumInts(int low, int high){ /* first check the BASE CASE */

if (low >= high) {printf(“Base Case: %5d.\n”, high);return(high);

} else {printf(“low = %3d + SumInts(%d,%d).\n”,

low, low+1, high);return(low + SumInts(low + 1, high));

}}

Page 20: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

Execution will produce the following output:

low = 5 + SumInts(6,10).low = 6 + SumInts(7,10).low = 7 + SumInts(8,10).low = 8 + SumInts(9,10).low = 9 + SumInts(10,10).Base Case: 10.

Not particularly informative. We could print both on entry and on exit from each function call - that might give a better picture.

19.102 - Computing II

Page 21: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

int SumInts(int low, int high){ int recResult;

/* first check the BASE CASE */ if (low >= high) {

printf(“Base Case: %5d.\n”, high);return(high);

} else {printf(“= %3d + SumInts(%d,%d).\n”,

low, low+1, high);recResult = SumInts(low + 1, high);printf(“= %3d + %3d.\n”, low, recResult); return(low + recResult);

}}

Page 22: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

Printout:

= 5 + SumInts(6,10).= 6 + SumInts(7,10).= 7 + SumInts(8,10).= 8 + SumInts(9,10).= 9 + SumInts(10,10).Base Case: 10.= 9 + 10.= 8 + 19.= 7 + 27.= 6 + 34.= 5 + 40.

SumInts gives 45

19.102 - Computing II

Page 23: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Better, but not particularly informative, since it gives us no sense of the depth of the recursion for each call: the whole thing is too flat. We need to introduce some other trick: how about spaces?

void EmitNSpaces(int spaces)

{ int index;

for (index = 0; index < spaces; index++)

printf(“ “);

}

Will print out any spaces number of spaces…

How do we keep track of how many spaces to print?

Add a parameter to the recursive function: level.

Page 24: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

int SumInts(int low, int high, int level){ int recResult;

/* first check the BASE CASE */ if (low >= high) {

EmitNSpaces(level);printf(“Base Case: %5d.\n”, high);return(high);

} else {EmitNSpaces(level)printf(“= %3d + SumInts(%d,%d).\n”,

low, low+1, high);recResult = SumInts(low + 1, high, level+1);EmitNSpaces(level);printf(“= %3d + %3d.\n”,

low, recResult); return(low + recResult);

}}

Page 25: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

The call SumInts(5, 10, 1);produces the output:

= 5 + SumInts(6,10). = 6 + SumInts(7,10). = 7 + SumInts(8,10). = 8 + SumInts(9,10). = 9 + SumInts(10,10). Base Case: 10. = 9 + 10. = 8 + 19. = 7 + 27. = 6 + 34. = 5 + 40.

SumInts gives 45

Page 26: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Add printed information about the level:int SumInts(int low, int high, int level){ int recResult;

/* first check the BASE CASE */ if (low >= high) {

printf(“Level = %3d; “,level); EmitNSpaces(level);printf(“ Base Case: %5d.\n”, high);return(high);

} else {printf(“Level = %3d; “,level);EmitNSpaces(level)printf(“%3d + SumInts(%d,%d).\n”,

low, low+1, high);recResult = SumInts(low + 1, high, level+1);printf(“Level = %3d; “,level);EmitNSpaces(level);printf(“%3d + %3d.\n”, low, recResult); return(low + recResult);

} }

Page 27: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

The call SumInts(5, 10, 1);produces the output:

Level = 1; 5 + SumInts(6,10).Level = 2; 6 + SumInts(7,10).Level = 3; 7 + SumInts(8,10).Level = 4; 8 + SumInts(9,10).Level = 5; 9 + SumInts(10,10).Level = 6; Base Case: 10.Level = 5; 9 + 10.Level = 4; 8 + 19.Level = 3; 7 + 27.Level = 2; 6 + 34.Level = 1; 5 + 40.

SumInts gives 45press enter:

19.102 - Computing II

Page 28: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Solution Methods and Depth of the Recursion Tree.

In the previous solution, the depth of the recursion (the maximum number of ACTIVE stack frames) is equal to the number of integers being summed: summing 1000 integers requires 1000 stack frames. This is not serious if we sum a few integers this way, but many thousands could be a problem. Using the formula instead would be the preferred way - no recursion and no loops, just one addition, one multiplication and one division…

There may be situations where we can’t find a convenient formula - or where the mere existence of such a formula makes no sense (we’ll see some later in the course).

Page 29: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

One of the solutions already shown lets us do MUCH better:int SumInts(int m, int n)

{ int mid;

// check BASE CASE (or cases…)

if (n < m) return(0); // overshot

else if (n == m) return(n); // endpoint

else

{

mid = (m + n)/2; // compute midpoint

return(SumInts(m, mid)

+ SumInts(mid + 1, n));

}

}

Page 30: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

The Formatted Output Function:int SumInts(int m, int n, int level){ int mid, leftH, rightH;

if (n < m) { // overshotprintf("Level = %3d; ",level); EmitNSpaces(level);printf("Error - Overshot: %5d.\n", 0);return(0);

} else if (n == m) {// endpoint - base caseprintf("Level = %3d; ",level); EmitNSpaces(level);printf("%d. Base Case.\n", n);return(n);

} else {mid = (m + n)/2; // compute midpointprintf("Level = %3d; ",level); EmitNSpaces(level);printf("SumInts(%d,%d) + SumInts(%d, %d). \n",m,mid,

mid+1,n);leftH = SumInts(m, mid,level + 1) ;rightH = SumInts(mid+1, n,level + 1);printf("Level = %3d; ",level); EmitNSpaces(level);printf("%d + %d. Returning Sum.\n",leftH, rightH);return(leftH + rightH);

}}

19.102 - Computing II

Page 31: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

The Formatted Output:Level = 1; SumInts(5,7) + SumInts(8, 10). Level = 2; SumInts(5,6) + SumInts(7, 7). Level = 3; SumInts(5,5) + SumInts(6, 6). Level = 4; 5. Base Case.Level = 4; 6. Base Case.Level = 3; 5 + 6. Returning Sum.Level = 3; 7. Base Case.Level = 2; 11 + 7. Returning Sum.Level = 2; SumInts(8,9) + SumInts(10, 10). Level = 3; SumInts(8,8) + SumInts(9, 9). Level = 4; 8. Base Case.Level = 4; 9. Base Case.Level = 3; 8 + 9. Returning Sum.Level = 3; 10. Base Case.Level = 2; 17 + 10. Returning Sum.Level = 1; 18 + 27. Returning Sum.

SumInts gives 45press enter:

19.102 - Computing II

Page 32: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

To compute SumInt(0, 10), the solution discussed earlier would require 11 stack frames active at the same time. How many active stack frames does this one require?

We’ll see that the actual number of stack frames CONSTRUCTED is still large (in fact, even larger), but NOT ALL OF THEM NEED TO BE ACTIVE AT THE SAME TIME: the amount of space required by the algorithm TO RUN is thus much less, although the total amount of space that needs to be MANAGED is more. This is an example of the rule: “there is no such thing as a free lunch”. Much of Computer Science is devoted to finding ways of making lunch cheaper, or at least well varied for roughly the same price. Remember that PRICE is not JUST execution time or space.

Page 33: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

0 10

0 5 6 10

0 2 3 5 86 9 10

0 1 2 2 3 4 5 5 6 7 8 8 9 9 1010

0 0 1 1 3 3 4 4 6 6 7 7

Page 34: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

As it turns out, this last algorithm requires a number of active stack frames roughly equal to the LOGARITHM in base 2 of the number of integers we want to sum…

1024 integers ≈ 10 stack frames;

1,000,000 integers ≈ 20 stack frames;

1,000,000,000 integers ≈ 30 stack frames;

Etc.

Not much space for an “inefficient recursive algorithm”…

Page 35: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

The number of stack frames actually constructed in the example is about 20, and would, in general, be no more than twice as many as by the previous method: we have traded more total stack frames for a MUCH smaller depth of recursion at any one time. We have gained what might be a large amount of space, by giving up some time - the time to allocate and deallocate twice as many stack frames.

Page 36: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

We have gained something else, though: by “dividing” we have made it possible to solve the subproblems independently. We could thus send one subproblem to one processor, and the other one to a second. The splitting of the subproblems into sub-subproblems would allow four processors to contribute to the solution, etc., up to the largest set of processors for which the overhead of keeping parallelism straight is more than what we gain by parallelizing (again, no free lunch).

What we have gained is OBVIOUS PARALLELISM - which may be a lot more important than the “trivial” doubling of a few stack frames...

Page 37: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Another example: fast raising to a power.

This works much better in programming environments where integers can be of arbitrary size, and not limited to a 32 (16?) bit ( 2 billion) representation. Here is the naïve way of doing it

long Power(long base, long exponent)

{ /* base != 0, exponent >= 0 */

if (exponent == 0) return(1);

else return(base*Power(base, exponent - 1));

}

This is fine, as long as the exponent is small (< a few dozen?) and the base is also small.

Page 38: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Unfortunately, secure encryption requires we deal with integers in the 150-200 decimal digit range… and beyond. Other fancy number theoretic studies require even bigger numbers.

A recursion which is 200 decimal digits deep in size? No matter how small the stack frame you allocate, there won’t be enough memory available in the universe to carry out the computation…

There won’t be enough time in the universe to complete it: at 1 nanosecond per frame, this means fewer than 1015 frames per day, which would then require 10185 days, or 3*10182 years to complete...

Page 39: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Here is a useful auxiliary function:

long Square(long x)

{

return(x*x);

}

Notice that it takes just ONE copy of the parameter, so that only one parameter evaluation is going to be needed. Once the value is obtained, it can be used TWICE. This is crucial.

Page 40: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

long FastPower(long base, long exponent)

{

if (0 == (exponent % 2)) /* is exponent even? */

return(Square(FastPower(base, exponent/2)));

else

return(base*FastPower(base, exponent - 1));

}

This will require at most 2*log2(exponent) multiplications, rather than (exponent - 1) multiplications. Fortunately, after every operation, we only need deal with the remainder of the result after division by a fixed large number, so the ACTUAL numbers we need to multiply will not fill up the universe: just 400 decimal digits for a 200 decimal digit encryption scheme.

Page 41: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

One should notice that log2(10200) ≈ 700, so that the maximum stack depth we could encounter is, roughly, 1400. Which is somewhat better than 10200.

All this could have been done non-recursively. Recursion is simply another technique for problem-solving: it gives us insights that other techniques either deny us, hide from us, or require the programming to be more complex.

Page 42: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

The functions in Maple V: (NOT C or C++ !!!!)

> FastPower := proc(base, exponent, modulo)> if (exponent = 0) then 1> elif (0 = (exponent mod 2)) then > (Square(FastPower(base,exponent/2,modulo),modulo) > mod modulo) > else ((base*FastPower(base, exponent - 1, modulo)) > mod modulo);> fi;> end:

> Square := proc(x, modulo) > ((x*x) mod modulo); > end:

19.102 - Computing II

Page 43: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

Example runs in Maple V (neither C nor C++ supports indefinite precision integer arithmetic):

> FastPower(123, 456, 78910); 34321

> FastPower(123456789101112131415, 9876543213579864211121314525758975321, 99887766554433221112131415161718192021222324252627282931987654321);36986746982596106906265409037450751115866469611492482412857379680

> FastPower(2, 16, 100000); 65536

19.102 - Computing II

Page 44: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Some List Manipulation Code.

It will be a little different from the code of the text, and will try to cover slightly different problems.

We start with a function that returns the contents of the first node of a list: this assumes we can return a structure - ANSI C does allow the return of any types OTHER than : a) array of T(where T is any type); b) function returning T. Also, structures CAN be assigned.

NodeInfo Head(NodeType *L)

{

return(L->Info);

}

Page 45: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

A function that returns the REST of a list:NodeType *Tail(NodeType *L)

{

return(L->Link);

}

A function that CONSTRUCTS a list:NodeType *Cons(NodeInfo info, NodeType *L)

{

NodeType *N;

N = (NodeType *)malloc(sizeof(NodeType));

N->Info = info;

N->Link = L;

return(N);

}

Page 46: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

A function that computes the LENGTH of a list:int Length(NodeType *L)

{ if (L == NULL) return(0);

else return(1 + Length(Tail(L)));

}

Its ITERATIVE form:int Length(NodeType *L)

{ int len = 0;

while (L != NULL) {

len++;

L = L->link;

}

return(len);

}

Page 47: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

A function that returns the pointer to the Nth element of the list:

NodeType *Nth(NodeType *L, int n)

{ if (L == NULL) return(NULL);

else if (n == 0) return(L);

else return(Nth(Tail(L), n - 1));

}

Note the 0 corresponds to the first (or zeroth?) element of the list.

A reasonable design question would be: do I return a pointer or do I return the object (i.e. the Info structure)? A way of answering it can be guided by the question: what do I do if I don’t find it?

Page 48: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

A DIFFERENT WAY OF IMPLEMENTING LINKED LISTS.

The current implementation of linked lists is not very general: the LINK and the INFORMATION fields appear together, in the SAME structure. All the list manipulation languages that have been developed over the decades opted for a different approach: separate the information from the links.

typedef NodeInfo…; /* this depends on the use */

typedef struct node {

NodeInfo *info;

node *Link;

} Node;

Page 49: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

A node is just TWO pointers. Since pointers are all of the same form (their type must be changed, but they all take up one word), it is possible to just RE-CAST a pointer to one of a different type, so that, at least in principle, the same pointer pairs can be used to represent list of objects of many types. Each list is normally assumed to contain objects of just ONE type.

Let’s see how this would work.

The EMPTY list:

Node *L;

L = NULL;

19.102 - Computing II

L

Page 50: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

The Head Function: for potential generality, C requires we return a pointer…

NodeInfo *Head(Node *L)

{ if (L == NULL) return(error);

else return(L->Info); // this is now a pointer // to an Info structure

}

L

info1

Info Link ….

Head(L)

Page 51: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

The Tail function:

Node *Tail(Node *L)

{ if (L == NULL) return(NULL);

else return(L->Link);

}

L

info1

Info Link ….

Tail(L)

Info Link

info2

Page 52: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

The Constructor function: how do we handle the incoming information? We will share structures, so DON’T make a copy.

Node *Cons(Info *info, Node *L)

{

Node *N;

N = (Node *)malloc(sizeof(Node));

N->Link = L; // Connect to existing front

N->Info = info; // Connect to the information

return(N);

}

Page 53: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

A picture, to firm up our ideas. Start with the empty list.

L info1

L

info1

Info Link

This is the original

L = Cons(&info1, L);

Page 54: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

L

info1

Info Link

19.102 - Computing II

Add a second element: L = Cons(&info2, L);

info2

L

info1

Info Link

info2

Info Link

Etc….

Page 55: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

The call L1 = Cons(&info2, L); would have resulted in the picture:

L1

info1

Info Link

info2

Info Link

L

Both the OLD and the NEW list are accessible.

Page 56: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

The textbook List Reversal function destroyed the original list. This approach allows us to reverse a list while leaving the original unchanged, and WITHOUT making copies of the information stored in it:

Node *Reverse(Node* L)

{

return(AuxReverse(L, NULL));

}

Node *AuxReverse(Node *L, Node *N)

{

if (L == NULL) return(N);

else return(AuxReverse(Tail(L),Cons(Head(L), N)));

}

Page 57: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

L1 = Reverse(L);

L

L1

Page 58: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Although AuxReverse is Tail-Recursive (and can thus run in constant space), here is a fully iterative version of Reverse:

Node *Reverse(Node* L)

{ Node *rev;

rev = NULL;

while (L != NULL) {

rev = Cons(Head(L), rev);

L = Tail(L);

}

return(rev);

}

Page 59: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Appending two lists, while leaving the originals unchanged:

Node *Append(Node *L1, Node *L2)

{ if (L1 == NULL) return(L2);

else return(Cons(Head(L1), Append(Tail(L1), L2)));

}

L1 L2

L3

L3 = Append(L1, L2);

Page 60: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Using Append, we can implement a different reversal:

Node *Reverse(Node *L)

{ if (L == NULL) return(NULL);

else return(Append(Reverse(Tail(L)),

Cons(Head(L), NULL)));

}

Call: L1 = Reverse(L);

UNDESIRABLE: WHY? Too many LOST nodes… Too many intermediate lists that are constructed and then irretrievably forgotten: C does NOT support automatic garbage collection, but this is NOT the worst problem. Even if it did, the algorithm would take too long to run with long lists...

Page 61: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

Some Comments on Infinite Regress.

One of the problems with all repetition constructs (loops and recursion) is the statement of the termination condition and the guarantee that the condition will occur after a finite number of repetitions.

A for construct is normally used as a counting loop: some index starts at some value; the index is checked against some condition (usually an inequality); the index is incremented according to some rule.

A while construct checks the value of some boolean expression before executing the body, within which the value of the expression is modified.

Page 62: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

A do … while construct performs the check after execution of the body.

It should be clear that in all of these repetition constructs there is a possibility of error that will lead to the repetition never terminating.

Recursion has the same problems:

A) at entry a check is performed to detect termination;

B) if the check fails, the rest of the body is executed, which will result in a recursive call to the same function with somewhat different parameter values.

There are at least two distinct ways in which this scheme will lead to “infinite regress” - equivalent to “infinite looping”.

Page 63: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

The first one is: the termination condition is never satisfied. Ex.:

long factorial(long n){

if (n == 0) return 1;

else return(n*factorial(n-1));

}

Call: factorial(-1);

Symptoms: program runs out of memory or continues running forever… (in this case, it will run out of memory).

Solution: make sure all inputs to the function are accounted for and lead to termination.

Page 64: 19.102 - Computing II Recursion: How to solve a problem by doing some work and sending the rest out to be handled the same way. Many mathematical functions

19.102 - Computing II

The second is: the parameters are not changed from one recursive call to the next.

long SumInts(long low, long high) {

long mid=(low + high)/2;

if (low > high) return(0);

else

return(SumInts(low, mid) +

SumInts(mid+1,high));

}

Call: SumInts(2,2);

Solution: make sure the parameters change from one call to the next.