[Instructions] [Search] [Current] [Syllabus] [Links] [Handouts] [Outlines] [Labs] [More Labs] [Assignments] [Quizzes] [Exams] [Examples] [Book] [Tutorial] [API]

*Do not read these notes until after you've turned in the assignment.*

As you may have noted, I've released this answer key before grading your homeworks. That's because I think you'll want to refer to it as you work on the exam. I'll add some general comments on your assignments after I've graded them.

As many of you have noted, this is a lot like a long math assignment. Just as in math assignments you don't get all of the problems graded, you will find that I will not grade all of the problems on this assignment.

You can find all the example programs in
`http://www.math.grin.edu/~rebelsky/Courses/CS152/99F/Examples/HW3/`

.

Do problems 24, 27, 28, and 29 of Chapter 5.

The following defines a function that calculates an approximation of
the square root of a number, *N*, starting with an approximate answer,
*A*, within the specified tolerance, *T*.

Sqrt(N,A,T)A, if |N-A^{2}| <=TSqrt(N, (A^{2}+N)/(2*A),T), if |N-A^{2}| >T

(a) What preconditions does Sqrt require in order to work correctly?

*N* must be nonnegative, since it is impossible to compute the
square root of a negative number.

*A* must be smaller than the square root of the largest possible
value that can be represented. It must also remain smaller, but I
don't know how one could guarantee that.

*A* must be nonzero (we divide by it).

*A* should be positive if we want a positive square
root.

*T* must be positive (it cannot be 0 or negative).

(b) Write a recursive version of `sqrt`

, using
the definition given above.

I've chosen a slightly different technique than normal. I've set up a general interface for ``things that can sort'' and then written a tester for that interface as well as an implementation with the recursive version. The recursive implementation also includes a main method (which I would typically put in a separate class, except that this eases testing).

importRootComputer;importRootTester;importSimpleOutput;/** * Compute the square root of a value supplied on the command * line. * Usage: * java RecursiveRoot value tolerance * If you'd like to know what steps it goes through, supply * an initial guess, too. * java RecursiveRoot value tolerance guess * * @author Samuel A. Rebelsky * @version 1.0 of October 1999 */publicclassRecursiveRootimplementsRootComputer {// +---------+-------------------------------------------------// | Methods |// +---------+/** * Compute the square root of num to within a specified tolerance, * using estimate guess as the current estimate. Uses Newton's * method for approximation. If observer is non-null, prints out * information at each step. */publicdoublesqrt(doublenum,doubleguess,doubletolerance, SimpleOutput observer) {// Observations:if(observer !=null) { observer.println("N:" + num + "; A:" + guess + "; T:" + tolerance); }// Base case: the guess is good enough.if(Math.abs(guess*guess-num) < tolerance) {returnguess; }// Recursive case: Improve the guess and try againelse{doublenewguess = (num + guess*guess) / (2*guess);returnsqrt(num, newguess, tolerance, observer); } } // sqrt(num,num,num)(// +------+----------------------------------------------------// | Main |// +------+publicstaticvoidmain(String[] args) {// Prepare for output.SimpleOutput out =newSimpleOutput();// Create something for testing.RootTester tester =newRootTester();// And testif(!tester.test(args,newRecursiveRoot(), out)) { out.println("Usage: java RecursiveRoot num tolerance"); out.println("Or: java RecursiveRoot num tolerance firstguess"); } } // main(String[]) } // class RecursiveRoot

Here's the test program.

importRootComputer;/** * A simple way to test objects that can compute square roots. * * @author Samuel A. Rebelsky * @version 1.0 of October 1999 */publicclassRootTester {/** * Run a RootComputer on values given by the command line. * The structure of the values is * num tolerance * Or * num tolerance firstGuess */publicbooleantest(String[] args, RootComputer computer, SimpleOutput out) {// Have we encountered an error?booleanerror =false;// Let the user know how to use this if it was started incorrectly.if((args.length < 2) || (args.length > 3)) {returnfalse; }// Set up the observer.SimpleOutput observer =null;// Get the number, tolerance, and guessdoublenum = 0;doubletol = 0;doubleguess = 1;try{ num = (newDouble(args[0])).doubleValue(); }catch(Exception e) { out.println("Error: '" + args[0] + "' is not a number."); error =true; }try{ tol = (newDouble(args[1])).doubleValue(); }catch(Exception e) { out.println("Error: '" + args[1] + "' is not a number."); error =true; }if(args.length == 3) { observer = out;try{ guess = (newDouble(args[2])).doubleValue(); }catch(Exception e) { out.println("Error: '" + args[2] + "' is not a number."); error =true; } } // is there a guess// Check valid values.if(num < 0) { out.println("Cannot compute square roots of negative numbers."); error =true; }if(tol <= 0) { out.println("Cannot compute square roots to that tolerance."); error =true; }// Has there been an error? If so, stop.if(error) {returnfalse; }else{doubleroot = computer.sqrt(num, guess, tol, observer); out.println("The square root of" + num + "is approximately" + root); out.println(root + "squared is" + (root*root));returntrue; } } // test(String[], RootComputer) } // class RootTester

Here's the interface.

importSimpleOutput;/** * Things that now how to approximate square roots. * * @author Samuel A. Rebelsky * @version 1.0 of October 1999 */publicinterfaceRootComputer {/** * Compute the square root of num using an initial approximation * and a specification of how close we want to be. Print out * information if the observer is non-null. */publicdoublesqrt(doublenum,doubleguess,doubletolerance, SimpleOutput observer); } // class RootComputer

Here's some sample output.

ji RecursiveRoot 3 0.0000001 5 N: 3.0; A: 5.0; T: 1.0E-7 N: 3.0; A: 2.8; T: 1.0E-7 N: 3.0; A: 1.9357142857142857; T: 1.0E-7 N: 3.0; A: 1.7427648919346335; T: 1.0E-7 N: 3.0; A: 1.7320837413295722; T: 1.0E-7 N: 3.0; A: 1.7320508078819778; T: 1.0E-7 The square root of 3.0 is approximately 1.7320508078819778 1.7320508078819778 squared is 3.000000001084612

(c) Write an iterative version of `sqrt`

, using
the definition given above.

importRootComputer;importRootTester;importSimpleOutput;/** * Compute the square root of a value supplied on the command * line. * Usage: * java IterativeRoot value tolerance * If you'd like to know what steps it goes through, supply * an initial guess, too. * java IterativeRoot value tolerance guess * * @author Samuel A. Rebelsky * @version 1.0 of October 1999 */publicclassIterativeRootimplementsRootComputer {// +---------+-------------------------------------------------// | Methods |// +---------+/** * Compute the square root of num to within a specified tolerance, * using estimate guess as the current estimate. Uses Newton's * method for approximation. If observer is non-null, prints out * information at each step. */publicdoublesqrt(doublenum,doubleguess,doubletolerance, SimpleOutput observer) {if(observer !=null) { observer.println("N:" + num + "; T:" + tolerance); }while(Math.abs(guess*guess-num) >= tolerance) {if(observer !=null) observer.println("A:" + guess); guess = (num + guess*guess) / (2*guess); } observer.println("Final estimate:" + guess);returnguess; } // sqrt(num,num,num,SimpleOutput)// +------+----------------------------------------------------// | Main |// +------+publicstaticvoidmain(String[] args) {// Prepare for output.SimpleOutput out =newSimpleOutput();// Create something for testing.RootTester tester =newRootTester();// And testif(!tester.test(args,newIterativeRoot(), out)) { out.println("Usage: java IterativeRoot num tolerance"); out.println("Or: java IterativeRoot num tolerance firstguess"); } } // main(String[]) } // class IterativeRoot

(d) Write a driver to test the versions of `sqrt`

Whoops. Did that already.

We want to count the number of possible paths to move from row 1,
column 1 to row *N*, column N in a two-dimensional grid.
Steps are restricted to going up one cell or going one cell to the
right. It is not possible to move diagonally.
The illustration shows three of many paths, with *N* = 10:

(a) The following function, `numPaths`

, is supposed to count the
number of paths, but it has some problems. Debug the function.

intnumPaths(introw,intcol,intn) {if(row == n)return1;elseif(col == n)returnnumPaths + 1;elsereturnnumPaths(row+1, col) * numPaths(row, col+1); } // numPaths(int, int, int)

The first problem with this code is that we don't know what it
does. That is, what do `row`

and `col`

have to do with the number of paths? It seems that this computes
``the number of paths from point (row,col) to (n,n)''.

There are some clear syntactic problems in this code.

- The first call to
`numPaths`

has no parameters. - The second and third calls don't have n.

But there are also some semantic problems with the code.

- If you're already in the nth row or column, there's only one path to (n,n), so the second return is incorrect. (That means we can correct two errors at once.)
- The number of paths from (row,col) to (n,n) is
``the number of paths if you go up first''
*plus*``the number of paths if you go right first''. The code uses times.

Putting it all together,

/** * Compute the number of paths from (1,1) to (n,n). N is given on * the command line. Is not very friendly about errors. * * @author Samuel A. Rebelsky * @version 1.0 of October 1999 */publicclassNumPaths {/** * The number of recursive calls. */protectedintcalls = 0;/** * Compute the number of paths from (row,col) to (n,n). * (row,col) should not be (n,n). */publicintnumPaths(introw,intcol,intn) { calls++;if(row == n)return1;elseif(col == n)return1;elsereturnnumPaths(row+1, col, n) + numPaths(row, col+1, n); } // numPaths(int, int, int)publicstaticvoidmain(String[] args) { SimpleOutput out =newSimpleOutput();intn = Integer.parseInt(args[0]); NumPaths helper =newNumPaths();intcount = helper.numPaths(1,1,n); out.println("There are" + count + "paths to (" + n + "," + n + ")"); out.println("That took" + helper.calls + "calls."); } // main(String[]) } // NumPaths

Here's some testing.

%ji NumPaths 4There are 20 paths to (4,4) That took 39 calls. %ji NumPaths 8There are 3432 paths to (8,8) That took 6863 calls. %ji NumPaths 9There are 12870 paths to (9,9) That took 25739 calls.

(b) After you have corrected the function, trace the execution
of `numPaths`

with `n`

= 4 by hand. Why is
this algorithm inefficient?

Note that I've boldfaced the part to be ``executed'' in each step and then italicized the result.

np(1,1,4)=np(2,1,4) + np(1,2,4)=np(2,1,4)+ np(1,2,4) =(np(3,1,4) + np(2,2,4))+ np(1,2,4) = (np(3,1,4)+ np(2,2,4)) + np(1,2,4) = ((np(4,1,4) + np(3,2,4))+ np(2,2,4)) + np(1,2,4) = ((np(4,1,4)+ np(3,2,4)) + np(2,2,4)) + np(1,2,4) = ((1+ np(3,2,4)) + np(2,2,4)) + np(1,2,4) = ((1 +np(3,2,4)) + np(2,2,4)) + np(1,2,4) = ((1 +(np(4,2,4) + np(3,3,4))) + np(2,2,4)) + np(1,2,4) = ((1 + (np(4,2,4)+ np(3,3,4))) + np(2,2,4)) + np(1,2,4) = ((1 + (1 + (np(4,3,4)+ np(3,4,4)))) + np(2,2,4)) + np(1,2,4) = ((1 + (1 + (1+ np(3,4,4)))) + np(2,2,4)) + np(1,2,4) = ((1 + (1 + (1 +np(3,4,4)))) + np(2,2,4)) + np(1,2,4) = ((1 + (1 + (1 +1))) + np(2,2,4)) + np(1,2,4) = ((1 + (1 +(1 + 1))) + np(2,2,4)) + np(1,2,4) = ((1 + (1 +2)) + np(2,2,4)) + np(1,2,4) = ((1 +(1 + 2)) + np(2,2,4)) + np(1,2,4) = ((1 +3) + np(2,2,4)) + np(1,2,4) = ((1 + 3)+ np(2,2,4)) + np(1,2,4) = (4+ np(2,2,4)) + np(1,2,4) = (4 +np(2,2,4)) + np(1,2,4) = (4 +(np(3,2,4) + np(2,3,4))) + np(1,2,4) = (4 + (np(3,2,4)+ np(2,3,4))) + np(1,2,4) = (4 + ((np(4,2,4) + np(3,3,4))+ np(2,3,4))) + np(1,2,4) = (4 + ((np(4,2,4)+ np(3,3,4)) + np(2,3,4))) + np(1,2,4) = (4 + ((1+ np(3,3,4)) + np(2,3,4))) + np(1,2,4) = (4 + ((1 +np(3,3,4)) + np(2,3,4))) + np(1,2,4) = (4 + ((1 +(np(4,3,4) + np(3,4,4))) + np(2,3,4))) + np(1,2,4) = (4 + ((1 + (np(4,3,4)+ np(3,4,4))) + np(2,3,4))) + np(1,2,4) = (4 + ((1 + (1+ np(3,4,4))) + np(2,3,4))) + np(1,2,4) = (4 + ((1 + (1 +np(3,4,4))) + np(2,3,4))) + np(1,2,4) = (4 + ((1 + (1 +1)) + np(2,3,4))) + np(1,2,4) = (4 + ((1 +(1 + 1)) + np(2,3,4))) + np(1,2,4) = (4 + ((1 +2) + np(2,3,4))) + np(1,2,4) = (4 + ((1 + 2)+ np(2,3,4))) + np(1,2,4) = (4 + (3+ np(2,3,4))) + np(1,2,4) = (4 + (3 +np(2,3,4))) + np(1,2,4) = (4 + (3 +(np(3,3,4) + np(2,4,4)))) + np(1,2,4) = (4 + (3 + (np(3,3,4)+ np(2,4,4)))) + np(1,2,4) = (4 + (3 + ((np(4,3,4) + np(3,4,4))+ np(2,4,4)))) + np(1,2,4) = (4 + (3 + ((np(4,3,4)+ np(3,4,4)) + np(2,4,4)))) + np(1,2,4) = (4 + (3 + ((1+ np(3,4,4)) + np(2,4,4)))) + np(1,2,4) = (4 + (3 + ((1 +np(3,4,4)) + np(2,4,4)))) + np(1,2,4) = (4 + (3 + ((1 +1) + np(2,4,4)))) + np(1,2,4) = (4 + (3 + ((1 + 1)+ np(2,4,4)))) + np(1,2,4) = (4 + (3 + (2+ np(2,4,4)))) + np(1,2,4) = (4 + (3 + (2 +np(2,4,4)))) + np(1,2,4) = (4 + (3 + (2 +1))) + np(1,2,4) = (4 + (3 +(2 + 1))) + np(1,2,4) = (4 + (3 +3)) + np(1,2,4) = (4 +(3 + 3)) + np(1,2,4) = (4 +6) + np(1,2,4) =(4 + 6)+ np(1,2,4) =10+ np(1,2,4) = 10 +np(1,2,4)= 10 +(np(2,2,4) + np(1,3,4))= 10 + (np(2,2,4)+ np(1,3,4)) = 10 + ((np(3,2,4) + np(2,3,4))+ np(1,3,4)) = 10 + ((np(3,2,4)+ np(2,3,4)) + np(1,3,4)) = 10 + (((np(4,2,4) + np(3,3,4))+ np(2,3,4)) + np(1,3,4)) = 10 + (((np(4,2,4)+ np(3,3,4)) + np(2,3,4)) + np(1,3,4)) = 10 + (((1+ np(3,3,4)) + np(2,3,4)) + np(1,3,4)) = 10 + (((1 +np(3,3,4)) + np(2,3,4)) + np(1,3,4)) = 10 + (((1 +(np(4,3,4) + np(3,4,4))) + np(2,3,4)) + np(1,3,4)) = 10 + (((1 + (np(4,3,4)+ np(3,4,4))) + np(2,3,4)) + np(1,3,4)) = 10 + (((1 + (1+ np(3,4,4))) + np(2,3,4)) + np(1,3,4)) = 10 + (((1 + (1 +np(3,4,4))) + np(2,3,4)) + np(1,3,4)) = 10 + (((1 + (1 +1)) + np(2,3,4)) + np(1,3,4)) = 10 + (((1 +(1 + 1)) + np(2,3,4)) + np(1,3,4)) = 10 + (((1 +2) + np(2,3,4)) + np(1,3,4)) = 10 + (((1 + 2)+ np(2,3,4)) + np(1,3,4)) = 10 + ((3+ np(2,3,4)) + np(1,3,4)) = 10 + ((3 +np(2,3,4)) + np(1,3,4)) = 10 + ((3 +(np(3,3,4) + np(2,4,4))) + np(1,3,4)) = 10 + ((3 + (np(3,3,4)+ np(2,4,4))) + np(1,3,4)) = 10 + ((3 + ((np(4,3,4) + np(3,4,4))+ np(2,4,4))) + np(1,3,4)) = 10 + ((3 + ((np(4,3,4)+ np(3,4,4)) + np(2,4,4))) + np(1,3,4)) = 10 + ((3 + ((1+ np(3,4,4)) + np(2,4,4))) + np(1,3,4)) = 10 + ((3 + ((1 +np(3,4,4)) + np(2,4,4))) + np(1,3,4)) = 10 + ((3 + ((1 +1) + np(2,4,4))) + np(1,3,4)) = 10 + ((3 + ((1 + 1)+ np(2,4,4))) + np(1,3,4)) = 10 + ((3 + (2+ np(2,4,4))) + np(1,3,4)) = 10 + ((3 + (2 +np(2,4,4))) + np(1,3,4)) = 10 + ((3 + (2 +1)) + np(1,3,4)) = 10 + ((3 +(2 + 1)) + np(1,3,4)) = 10 + ((3 +3) + np(1,3,4)) = 10 + ((3 + 3)+ np(1,3,4)) = 10 + (6+ np(1,3,4)) = 10 + (6 +np(1,3,4)) = 10 + (6 +(np(2,3,4) + np(1,4,4))) = 10 + (6 + (np(2,3,4)+ np(1,4,4))) = 10 + (6 + ((np(3,3,4) + np(2,4,4))+ np(1,4,4))) = 10 + (6 + ((np(3,3,4)+ np(2,4,4)) + np(1,4,4))) = 10 + (6 + (((np(4,3,4) + np(3,4,4))+ np(2,4,4)) + np(1,4,4))) = 10 + (6 + (((np(4,3,4)+ np(3,4,4)) + np(2,4,4)) + np(1,4,4))) = 10 + (6 + (((1+ np(3,4,4)) + np(2,4,4)) + np(1,4,4))) = 10 + (6 + (((1 +np(3,4,4)) + np(2,4,4)) + np(1,4,4))) = 10 + (6 + (((1 +1) + np(2,4,4)) + np(1,4,4))) = 10 + (6 + (((1 + 1)+ np(2,4,4)) + np(1,4,4))) = 10 + (6 + ((2+ np(2,4,4)) + np(1,4,4))) = 10 + (6 + ((2 +np(2,4,4)) + np(1,4,4))) = 10 + (6 + ((2 +1) + np(1,4,4))) = 10 + (6 + ((2 + 1)+ np(1,4,4))) = 10 + (6 + (3+ np(1,4,4))) = 10 + (6 + (3 +np(1,4,4))) = 10 + (6 + (3 +1)) = 10 + (6 +(3 + 1)) = 10 + (6 +4) = 10 +(6 + 4)= 10 +10=10 + 10=20

(c) We can improve the efficiency of this algorithm by using dynamic
programming. In this case, we use a *two-dimensional* array
of integer values. This keeps the function from having to recalculate
values that it has already done. Design and code a version of
`numPaths`

that uses this approach.

Here is my revised function. I've used the same technique that
you saw in the book. I've used

rather than
__long__

because I expect to deal with bigger numbers
in my examples.
__int__

/** * Compute the number of paths from (1,1) to (n,n). N is given on * the command line. Is not very friendly about errors. Uses * a two-dimensional array to keep track of previously computed * values. * * @author Samuel A. Rebelsky * @version 1.0 of October 1999 */publicclassNewNumPaths {/** The number of recursive calls. */protectedlongcalls = 0;/** * A two dimensional array to keep track of the number of paths * from (x,y) to (n,n). pathsFrom[x-1][y-1] gives the number * of paths from (x,y) to (n,n). If it is not yet set at a * particular position, it has the value -1 in that position. */protectedlong[][] pathsFrom;/** * Compute the number of paths from (row,col) to (n,n). * (row,col) should not be (n,n). */publiclongnumPaths(introw,intcol,intn) { calls++;// Base cases:if((row == n) || (col == n)) {return1; }else{// Make sure that the array is initialized.if(pathsFrom ==null) { pathsFrom =newlong[n][n];for(inti = 0; i < n; ++i)for(intj = 0; j < n; ++j) pathsFrom[i][j] = -1; } // if the array is uninitialized// Is the array filled in for (row,col)? If not, fill it in.if(pathsFrom[row-1][col-1] == -1) { pathsFrom[row-1][col-1] = numPaths(row+1,col,n) + numPaths(row,col+1,n); }// Return the value from the array.returnpathsFrom[row-1][col-1]; } // "recursive" case } // numPaths(int, int, int)publicstaticvoidmain(String[] args) { SimpleOutput out =newSimpleOutput();intn = Integer.parseInt(args[0]); NewNumPaths helper =newNewNumPaths();longcount = helper.numPaths(1,1,n); out.println("There are" + count + "paths to (" + n + "," + n + ")"); out.println("That took" + helper.calls + "calls."); } // main(String[]) } // NewNumPaths

(d) Show an invocation of the version of `numPaths`

in Part (c),
including any array initialization necessary.

No array initialization is necessary; I've made it part of the function. Here are some sample runs.

%ji NewNumPaths 4There are 20 paths to (4,4) That took 19 calls. %ji NewNumPaths 8There are 3432 paths to (8,8) That took 99 calls. %ji NewNumPaths 9There are 12870 paths to (9,9) That took 129 calls. %ji NewNumPaths 15There are 40116600 paths to (15,15) That took 393 calls.

(e) Rewrite `numPaths`

iteratively (still using the table).

Here is my solution. Note that we had to fill in the array ``backwards'' (from the end to the beginning).

/** * Compute the number of paths from (1,1) to (n,n). N is given on * the command line. Is not very friendly about errors. Uses * a two-dimensional array to keep track of previously computed * values. * * @author Samuel A. Rebelsky * @version 1.0 of October 1999 */publicclassIterativeNumPaths {/** Are we testing? */protectedbooleantesting =false;/** * Compute the number of paths from (row,col) to (n,n). * (row,col) should not be (n,n). */publicintnumPaths(introw,intcol,intn) {// A two dimensional array to keep track of the number of paths// from (x,y) to (n,n). pathsFrom[x-1][y-1] gives the number// of paths from (x,y) to (n,n).int[][] pathsFrom =newint[n][n];// We can fill in the last row and column. There is only// one path from (x,n) or (n,y) to (n,n) .for(inti = 0; i < n; ++i) { pathsFrom[i][n-1] = 1; pathsFrom[n-1][i] = 1; }// Move backwards, a row at a time, filling in the distances.for(intx = n-2; x >= 0; x--)for(inty = n-2; y >= 0; y--) pathsFrom[x][y] = pathsFrom[x+1][y] + pathsFrom[x][y+1];// Testing: Print out the arrayif(testing) { SimpleOutput out =newSimpleOutput();for(intx = 1; x <= n; ++x) {for(inty = 1; y <= n; ++y) { out.print(pathsFrom[x-1][y-1] + ""); } out.println(); } } // if testing// That's it, we're done.returnpathsFrom[row-1][col-1]; } // numPaths(int, int, int)publicstaticvoidmain(String[] args) { SimpleOutput out =newSimpleOutput();intn = Integer.parseInt(args[0]); IterativeNumPaths helper =newIterativeNumPaths();intcount = helper.numPaths(1,1,n); out.println("There are" + count + "paths to (" + n + "," + n + ")"); } // main(String[]) } // IterativeNumPaths

Here are some examples.

%ji IterativeNumPaths 4There are 20 paths to (4,4) %ji IterativeNumPaths 8There are 3432 paths to (8,8) %ji IterativeNumPaths 9There are 12870 paths to (9,9) %ji IterativeNumPaths 15There are 40116600 paths to (15,15)

(f) How do the various versions of `numPaths`

compare in
terms of time efficiency? Space efficiency? Clarity?

**Running Time**

The initial recursive solution is atrocious in terms of running time.
It's difficult to write the recurrence relation, since we recurse on two
different terms. I'll write f_{n}(a,b) for ``the amount of
time to compute `numPaths(a,b,n)`

'' like

- f
_{n}(n,b) = 1 - f
_{n}(a,n) = 1 - f
_{n}(a,b) = f_{n}(a+1,b) + f_{n}(a,b+1)

We note that

- f
_{n}(a+1,b) > f_{n}(a+1,b+1) - f
_{n}(a,b+1) > f_{n}(a+1,b+1)

We can write

- f
_{n}(a,b) <= 2*f_{n}(a+1,b+1)

So

- f
_{n}(1,1) - f
_{n}(1,1) <= 2*f_{n}(2,2) - f
_{n}(1,1) <= 2*2*f_{n}(3,3) - f
_{n}(1,1) <= 2*2*2*f_{n}(4,4) - f
_{n}(1,1) <= 2^{k}*f_{n}(1+k,1+k)

That is, f_{n}(1,1) is bounded below by 2^^{n}. Yowch!

The other two are approximately O(n^{2}), since both fill
in an n-by-n array.

**Space Efficiency**

We might be somewhat worried by the depth of recursion in the
highly-recursive version. It seems that the recusion stack should
not get more than O(n) deep, but I wouldn't swear to it (it's also
beyond the scope of what I'd expect you to do). The other two
clearly use O(n^{2}) space.

**Clarity**

Until you explain what's going on, it seems that none of these is very clear. The array makes it even less clear. Of these, I think the recursive one with the array strikes the best balance: once you understand the array, the way it's filled in makes sense. Your opinion may differ.

The Collatz sequences (also known as the Ulam sequences and the
3*X*+1 sequences) are defined recursively as:

f(X+1) = f(X)/2, if f(X) is even f(3*X+1), if f(X) is odd

Note that there is no base case here. That's because there are multiple Collatz sequences, which depend on your selection of the first value. For example, the Collatz sequence starting with 1 is: 1, 4, 2, 1, 4, 2, .... (1 is odd, so the next element is 1*3+1. 4 is even, so the next element is 4/2. 2 is even, so the next element is 1.) Similarly, the Collatz sequence starting with 16 is: 16, 8, 4, 2, 1, 4, 2, 1, ....

(a) What is the Collatz sequence starting with 7?

7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1, 4, 2, 1, ...

(b) What is the Collatz sequence starting with 8?

8, 4, 2, 1, 4, 2, 1, ...

(c) What is the Collatz sequence starting with 15?

15, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1, 4, ...

As you may have noted, the values in the Collatz sequences we've examined rise and fall. One might want to write a function that keeps stepping through the Collatz sequence until the current value reaches a particular range. We might write that function as:

publicintcollatzInRange(intx,intlower,intupper) {if((x >= lower) && (x <= upper))returnx;elseif(x % 2 == 0)returncollatzInRange(x/2, lower, upper);elsereturncollatzInRange(3*x+1, lower, upper); }

(a) What problems come up in verifying this function?

In our typical analysis of recursive functions, we want to be able
to verify termination by using the ``smaller caller'' criterion.
But it's hard to tell whether `x`

gets closer to the
range. (Our examples earlier suggest that the sequence varies
wildly.)

(b) Would any preconditions help you solve those problems?

No, not really.

(c) You may have observed that all the Collatz sequences we've looked at eventually resolve themselves to the cycle 1,4,2,1,4,2,.... Can you find a sequence that doesn't?

I decided to write a short program that lets me generate Collatz sequences.

importSimpleOutput;/** * Give a sequence of 100 Collatz numbers starting with whatever * value is given on the command line. Does not do any * error checking of input. * * @author Samuel A. Rebelsky * @version 1.0 of October 1999 */publicclassCollatz {publicstaticvoidmain(String[] args) {// Get a starting value.longval = Integer.parseInt(args[0]);// Prepare for output.SimpleOutput out =newSimpleOutput();// Output 100 valuesfor(inti = 0; i < 100; ++i) {// Print the current valueout.print(val + "");// Move on to the next valueif(val % 2 == 0) val = val / 2;elseval = 3*val + 1; } // for out.println(); } // main(String[]) } // class Collatz

I then tried to generate a lot of Collatz sequences (starting with odd numbers, since even numbers ``degnerate'' to smaller odd numbers). 27 is interesting, since it takes a long time to get to 4, 1, 2 (but it still does). 31 also takes awhile. I quickly realized that I wasn't getting anywhere. Time to make the computer help more. I did note in my experiments that the sequence can get really big (over 9000) before settling down.

I'll admit that after trying the values from 1 to 1000, I could not find
a sequence that didn't settle down to 1,4,2. I then tried an Internet
search and found a number of articles on the Collatz problem. One
at `http://www.treasure-troves.com/math/CollatzProblem.html`

indicates that it seems that all positive values less than
3*2^{53} have been tested.

However, if you start with negative numbers (or with 0), it's clear you can get other sequences.

- 0, 0, 0, ...
- -1, -2, -1, -2, ...
- -5, -14, -7, -20, -10, -5, ...

Determine the running time (Big-O notation) of each of the following algorithms. Note that you may have to come up with an appropriate metric for the size of each problem.

Suppose you have N locations connected by sidewalks.To find the shortest path from location A to location B ...List all the paths from A to B Find the distance of each path. Take the smallest of all those values.

- There is one path that goes directly from A to B.
- There are N-2 paths that use two sidewalks. That is, that stop at one location between A and B (since there are N-2 locations other than A and B).
- There are (N-2)*(N-3) paths that use three sidewalks (stop at two locations between A and B). We choose one, C, of the N-2 other than A or B. We then go to another one, D, of the N-3 other than A, B, or C. We then go to B.
- Generalizing, there are (N-2)*(N-3)*...*(N-K) paths that use K sidewalks.
- When K = N-1 (the worst case), there are (N-2)*(N-3)*...*1 different possible paths.
- That is (N-2)! factorial. This is O(N!).

To find the smallest value in a collection ...Set smallestSoFar to one value in the collection While unchecked values remain in the collectoin Pick one If that value is smaller than guess then Set smallestSoFar to that value Return smallestSoFar

We look at each value once. There are N values. Hence, this is O(N).

To find the smallest value in a collection ...If the collection has only one element Return that element Otherwise Split the collection into two equal halves Find the smallest value in each half Return the smaller of those two values

We can do this the easy way: we look at each value once. There are n values, so this is still O(n). While this doesn't count the split and other stuff, we do note that each time we split, we look at at least one values.

We can also use recurrence relations. Let f(n) be the running time.

- f(1) = 1
- f(n) = 1 + 2*f(n/2) + 1

That is, 1 step to split (using our easy split technique with arrays), 2 recursive calls with half the collection, and 1 step to join the results.

Working that out for a few steps.

- f(n) = 2 + 2*f(n/2)
- f(n) = 2 + 2*(2 + 2*f(n/2/2))
- f(n) = 2 + 4 + 4*f(n/4)
- f(n) = 2 + 4 + 4*(2 + 2*(f(n/4/2)))
- f(n) = 2 + 4 + 8 + 8*f(n/8)

Generalizing,

- f(n) = 2
^{1}+ 2^{2}+ ... 2^{k}+ 2^{k}*f(n/2^{k})

When k = log_{2}n,

- f(n) = 2
^{1}+ 2^{2}+ ... 2^{log2n}+ 2^{log2n} - f(n) = 2
^{1}+ 2^{2}+ ... n + n. - f(n) = 2 + 4 + 8 + ... + + n/4 + n/2 + 2*n

We note that all the early stuff adds up to less than n. So

- f(n) <= 3*n

Hence, f(n) is in O(n).

To find the smallest difference between any two different numbers in a collection ...Set estimate to ``infinity'' For each value, v, in the collection For each value, u, not equal to v If |u-v| < estimate then Set estimate to |u-v| Return estimate

There are two nested loops.

- The outer loop repeats n times.
- In each repetition, we do the inner loop.
- The inner loop repeats n-1 times for one step of the outer loop.

Hence, this is an O(n^{2}) algorithm.

Thursday, 7 October 1999

- Created.
- Copied general structure from assignment 3.
- Added text of problems from
*Java Plus Data STructures*

Saturday, 10 October 1999

- Filled in answers up to the Collatz problem.

Sunday, 11 October 1999

- Worked on more ideas for the Collatz problem.

Monday, 12 October 1999

- Filled in answers for the second half.

[Instructions] [Search] [Current] [Syllabus] [Links] [Handouts] [Outlines] [Labs] [More Labs] [Assignments] [Quizzes] [Exams] [Examples] [Book] [Tutorial] [API]

**Disclaimer** Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.

This page may be found at http://www.math.grin.edu/~rebelsky/Courses/CS152/99F/Assignments/notes.03.html

Source text last modified Mon Oct 11 09:44:21 1999.

This page generated on Mon Nov 1 09:48:11 1999 by Siteweaver. Validate this page's HTML.

Contact our webmaster at rebelsky@grinnell.edu