I see two important computing concepts in your question. The first is binary search, the second is recursion. Since this is homework, here is a contribution towards understanding a binary search, recursion and how to think about them.
Think of binary search as dividing the solution "space" in half, keeping the half the solution is in and doing that in succession so that the process converges on the solution. The key concepts for doing this are that you need to engineer a solution "space" that has the following properties:
1) can be subdivided, usually in half or at least two pieces
2) of the two pieces after subdivision, there is a way to determine which half has the solution so that the process can be repeated on only one half.
Recursion involves a function (method in O-O speak) invoking itself. Recursion works really well for a process that converges to a conclusion. It either recurses forever or until you run out of some resource, usually memory, and it fatally stops. The two key concepts for recursion are:
1) convergence through some invariance (more on invariance below).
2) termination condition (one that recognizes sufficient convergence).
Now, for your square root routine. The requirements for the routine are:
1) Integer input.
2) Integer square-root approximation that gives the floor integer closest to the actual square root.
3) Use recursion.
4) Use binary search.
It helps to know some mathematics about square roots for this. Elementary calculus and analytical geometry concepts are helpful too. Lets do some reasoning.
We have an arbitrary positive integer x. We want its root y. If we choose some test value for y, we can see if it is the root of x if y * y = x. If y is too big, y * y > x. if y is too small, y * y < x. We also know that 0 <= root <= x and that square-roots of 0 and 1 are trivially zero and 1. Since we are looking for largest integer where y * y <= x (i.e. a floor value) we'll have to account for that too.
Here is some mathematical reasoning to help. We know that x = y * y where y is the square root of x. That means: y = x/y.
Hmmm... what happens if y is to large to be the square root of x? Then: x < y * y and: x/y < y which means x/y is also too small to be the square root of x. So we know that, for y too large, x/y < square-root of x < y. So, lets find a new y, say y1, between x/y and y as a new test value. The average of x/y and y will do. y1 = (x/y0 + y0)/2 will give a y1 that is closer to the square root of x than y0 if y0 is too large.
Does this converge? Well, in mathematics using positive real numbers, the average will always be above the value but getting closer each iteration. This satisfies the condition that we successively divide the solution "space" into two parts and know which of the two to keep. In this case, we successively calculate new values below previous ones and below which the answer still lies, allowing us to discard all values above the new one. We stop when we reach a condition where no more new values above the answer exist. Using computers, however, results in binary approximations of real numbers. With integers, there is truncation in division. This may affect the convergence beneficially or adversely. In addition, your answer is supposed to be the largest integer smaller than or equal to the square root. It's wise to take a look at the kind of convergence we will get.
Because of integer division turncation, y1 = (x/y0 + y0)/2 will converge until successive iterations reach an integer root or a floor value for (i.e. the largest integer less than) the root. This is ideal. If we start with a proposed value for the root that has to be larger than the root, say x itself, the first value for yn where yn * yn <= x is the desired result.
The simple answer is that, when we start with y0 > y, the first new yn that is less than or equal to y, then y - yn < 1. That is, yn is now the floor value for which we've been looking and we now have a termination condition that exactly satisfies the conditions for the required answer.
Here are basic iterative and recursive solutions. The solutions don't incude safety features to ensure negative values are not input for x. The one major concern is to avoid dividing by zero in case someone wants to find the square root of 0. Since that is a trivial answer, both the recursive and iterative methods return 0 before division by zero can take place. Both the recursive and iterative solutions work with the trivial cases for finding the square roots of 0 and of 1.
There is another analysis that always has to be done with int and long arithmetic in Java. A major concern is integer overflow since Java does nothing about int or long overflow. Overflow results in twos-complement values (look that up elsewhere) that can lead to bogus results and Java does not throw exceptions with int or long overflow.
In this case, it is easy to avoid arithmetic that could result in an internal overflow with large values of x. If we create a termination condition such as y0 * y0 < x we risk overflow if x is greater than the square root of Integer.MAX_VALUE since y0 * y0, an intermediate value, will immediately exceed the maximum int value. However, we can rearrange the termination condition to y0 < x / y0. We still have a problem with the calculations: ((x / y0) + y0) / 2) if x and y0 are Integer.MAX_VALUE since it wll attempt Integer.MAX_VALUE + 1. However, we can always start with a value less than x that is guaranteed to be > y. x / 2 works for all values of x > 1. Since the square root of x where x is either 0 or 1 is simply x, we can easily test for those values and simply return the correct and trivial value. You can construct code to prevent using values < 0 or values > Integer.MAX_VALUE. The same can be applied if we use long instead of int. Welcome to computing in the real world!
public static int intSqRootRecursive (int x) {
// square roots of 0 and 1 are trivial and x / 2 for
// the y0 parameter will cause a divide-by-zero exception
if (x == 0 || x == 1) {
return x;
}
// starting with x / 2 avoids overflow issues
return intSqRootRecursive (x, x / 2);
} // end intSqRootRecursive
private static int intSqRootRecursive(int x, int y0) {
// square roots of 0 and 1 are trivial
// y0 == 0 will cause a divide-by-zero exception
if (x == 0 || x == 1) {
return x;
} // end if
if (y0 > x / y0) {
int y1 = ((x / y0) + y0) / 2;
return intSqRootRecursive(x, y1);
} else {
return y0;
} // end if...else
} // end intSqRootRecursive
public static int intSqRootIterative(int x) {
// square roots of 0 and 1 are trivial and
// y == 0 will cause a divide-by-zero exception
if (x == 0 || x == 1) {
return x;
} // end if
int y;
// starting with y = x / 2 avoids overflow issues
for (y = x / 2; y > x / y; y = ((x / y) + y) / 2);
return y;
} // end intSqRootIterative
You can test the recursive solution to find out how many instances will result on the frame stack, but you will see that it converges very fast. It's interesting to see that the iterative solution is much smaller and faster than the recursive one, something that is often not the case and is why recursion gets used where it can be predicted that stack resources are sufficient for the recursion depth.