Search code examples
javastack-overflowbisection

Stack Overflow during Binary Search


I am using binary search to find a balance point between the planets. The method binaryBalance takes in Arraylist of planets which is an object with displacement and mass property. It also takes in the displacement of two planets between which I am trying to find a balance point of. Double x is the inital starting point of the search and I am setting the average displacement of p1 and p2 here. The code runs smoothly but it is off the answer for a minute amount. I try to increase the precision by setting the error interval to more than 1e-10, but I keep getting Stack Overflow error. How do I solve this problem with higher precision?

import java.util.*;
import java.lang.*;

public class Solution {
public static void main(String[] arg) {
    Scanner sc = new Scanner(System.in);
    int numCase = sc.nextInt();

    for (int k = 1; k <= numCase; k++) {

        //Initializing Space...
        int numPlanets = sc.nextInt();
        ArrayList<Planet> space = new ArrayList<>();
        int[] weights = new int[numPlanets];
        int[] displacements = new int[numPlanets];

        for (int i = 0; i < numPlanets; i++) {
            displacements[i] = sc.nextInt();
        }
        for (int i = 0; i < numPlanets;i++) {
            weights[i] = sc.nextInt();
        }
        for (int i = 0; i < numPlanets;i++) {
            Planet p = new Planet(displacements[i],weights[i]);
            space.add(p);
        }

        System.out.print("#" + k + " ");
        for (int i = 0; i < numPlanets-1; i++) {
            double init = (double) (space.get(i).getDisplacement() + space.get(i+1).getDisplacement()) /2;
            binaryBalance(space,space.get(i).getDisplacement(),space.get(i+1).getDisplacement(),init);
        }
        System.out.println();
    }
}

public static class Planet {
    private int d;
    private int m;

    public Planet(int d,int m) {
        this.d = d;
        this.m = m;
    }

    public void setDisplacement(int d) {
        this.d = d;
    }

    public void setMass(int m) {
        this.m = m;
    }

    public double getG(double dPlanet) {
        double betweenDistance = this.d - dPlanet;
        return this.m/(betweenDistance*betweenDistance);
    }

    public int getDisplacement() {
        return d;
    }
    public int getMass() {
        return m;
    }
}

public static void binaryBalance(ArrayList<Planet> space, double p1, double p2, double x) {
    double leftg = 0;
    double rightg = 0;
    for (int i = 0; i < space.size(); i++) {
        if (space.get(i).getDisplacement() < x) {
            leftg = leftg + space.get(i).getG(x);
        } else {
            rightg = rightg + space.get(i).getG(x);
        }
    }

    if (Math.abs(leftg - rightg) < 1e-10) {
        System.out.print(String.format("%.10f",x) + " ");
        return;
    }
    if (leftg < rightg) {
        binaryBalance(space, p1, x, (p1 + x) / 2);
    } else {
        binaryBalance(space, x, p2, (p2 + x) / 2);
    }
}

Test Cases are:

10
2
1 2 1 1
2
1 2 1 1000
2
457 468 333 321
3
1 2 3 1 2 1
4
2 3 5 7 3 2 7 5
5
3 11 12 19 29 542 661 450 521 366   
6
42 75 88 94 113 144 669 551 355 344 294 155
7
62 86 279 323 363 516 579 810 749 736 297 136 107 52
8
10 34 64 73 93 97 101 122 466 463 441 373 315 292 225 83
10
9 14 38 39 48 73 179 190 207 302 560 497 640 722 437 259 449 470 709 520

And the expected answer is:

#1 1.5000000000
#2 1.0306534300
#3 462.5504629633
#4 1.4060952085 2.5939047915
#5 2.5328594461 3.7271944335 6.0999536409
#6 6.3428568767 11.5477377494 15.9641592998 24.9267991615
#7 57.8805685415 81.8651598883 91.0573691382 105.0835650491 133.2934094881
#8 74.2211477711 190.6837563313 305.8269181686 348.3304429927 470.2694219293 555.4943093854
#9 21.5171374463 47.9890597763 68.6536668433 82.9131954023 95.0052272762 99.1999097770 116.4978330953
#10 11.5573600056 24.0238341337 38.4847676134 44.6137453708 64.7500445424 126.9908128982 184.3221650927 197.9760596291 266.0574653677

Solution

  • With the leftg-rightg tolerance of 1e-10, the greatest number of iterations is 47, on the second case where the masses are so different. That won't overflow any stacks, but of course you asked about increasing the accuracy. Unfortunately, it's impossible to even reach 1e-11 on case 6, because of the scale of the numbers involved (as I mentioned in a comment). So you get infinite recursion if you change the tolerance exponent at all.

    But maybe a fixed balance tolerance isn't what you're expected to do for this exercise! I get exactly the expected answers (to their given precision) if I instead refine until the interval has "zero" width. (p1 and p2 need not be equal, but there are no floating-point numbers between them. You detect this by noticing that x==p1 || x==p2; x will be whichever ends in a 0 in binary.) This takes at most 53 bisections for these cases: a number which should be familiar to any numerical analyst since it is the effective number of bits in the significand of a double. I didn't check whether a larger p2-p1 tolerance might give the correct answers.

    Since it's useless to get (much) above 53 levels deep, the choice of recursion here is harmless (although tail recursion with a void function looks very odd), and changing to iteration won't help at all. Either way, you do have to make sure that it terminates!