【问题标题】:Recursive algorithm stops working after certain depth递归算法在一定深度后停止工作
【发布时间】:2018-05-20 11:34:40
【问题描述】:

我在探索 Fork/Join 框架及其通过阶乘计数可能带来的速度优势时,发现我的顺序递归算法在某个点中断。准确地说,当我尝试计算46342! 时,RecursiveCounter 的结果是错误的,但在该值之前它总是正确的,并且与ParallelCounterLoopCounter 的结果相同。有谁知道为什么会发生这种情况?

以下是课程:

递归计数器:

public class RecursiveCounter implements FactorialCounter, RangeFactorialCounter {
    @Override
    public BigInteger count(int number) {
        return count(1, number);
    }

    @Override
    public BigInteger count(int from, int to) {
        int middle = (from + to) >> 1;
        BigInteger left;
        BigInteger right;
        if (middle - from > 1)
            left = count(from, middle);
        else
            left = new BigInteger(String.valueOf(from * middle));
        if (to - (middle + 1) > 1)
            right = count(middle + 1, to);
        else
            right = to == middle + 1 ? new BigInteger(String.valueOf(to)) : new BigInteger(String.valueOf((middle + 1) * to));
        return left.multiply(right);
    }
}

循环计数器:

public class LoopCounter implements FactorialCounter, RangeFactorialCounter {
    @Override
    public BigInteger count(final int number) {
        return count(1, number);
    }

    @Override
    public BigInteger count(final int from, final int to) {
        BigInteger result = new BigInteger("1");
        for (int i = from; i < to + 1; i++) {
            result = result.multiply(new BigInteger(String.valueOf(i)));
        }
        return result;
    }
}

ParallelCounter 的 RecursiveTask:

public class FactorialTask extends RecursiveTask<BigInteger> {
    private static final int THRESHOLD = 1000;
    private RangeFactorialCounter iterativeCounter = new LoopCounter();

    private Integer firstVal;
    private Integer lastVal;

    public FactorialTask(Integer from, Integer to) {
        super();
        this.firstVal = from;
        this.lastVal = to;
    }

    @Override
    protected BigInteger compute() {
        return count(firstVal, lastVal);
    }

    private BigInteger count(int from, int to) {
        int middle = (from + to) >> 1;
        if (to - from > THRESHOLD) {
            List<FactorialTask> tasks = Arrays.asList(new FactorialTask(from, middle), new FactorialTask(middle + 1, to));
            tasks.forEach(RecursiveTask::fork);
            return tasks.stream()
                    .map(RecursiveTask::join)
                    .map(BigInteger.class::cast)
                    .reduce(new BigInteger("1"), BigInteger::multiply);
        } else {
            return (from != to) ? countSequential(from, to) : new BigInteger(String.valueOf(from));
        }
    }

    private BigInteger countSequential(int from, int to) {
        return iterativeCounter.count(from, to);
    }
}

【问题讨论】:

  • 预期结果是什么?它可能会超过BigInteger 最大值。
  • @c0der 我对此表示怀疑,因为其他两个实现返回相同的结果,这也是正确的。而且BigInteger bound 相当高,老实说,2^(2^31) 如果我没记错的话
  • @c0der BigInteger 最多可以包含~10^646456993 数字,但46342! 仅是~10^196107 数字

标签: java math recursion integer biginteger


【解决方案1】:

RecursiveCounterfrom * middle(middle + 1) * to可能会溢出,你需要使用BigInteger来操作它们:

...
left = BigInteger.valueOf(from).multiply(BigInteger.valueOf(middle));
...
right = to == middle + 1 ? BigInteger.valueOf(to) : BigInteger.valueOf(to).multiply(BigInteger.valueOf(middle + 1));

那么你可以在RecursiveCounterLoopCounter得到同样的结果:

LoopCounter loopCounter = new LoopCounter();
RecursiveCounter recursiveCounter = new RecursiveCounter();
BigInteger loopResult = loopCounter.count(46342);
BigInteger recursiveResult = recursiveCounter.count(46342);
System.out.println(loopResult.equals(recursiveResult)); // true

【讨论】:

    【解决方案2】:

    发生这种情况是因为 int 的数值溢出,而不是因为递归深度,这是由您的算法很好地控制的,它需要 O(log2n) 个堆栈帧进行递归。

    溢出发生在这里:

    new BigInteger(String.valueOf((middle + 1) * to))
    

    to 为高时,此值可能会溢出int。具体来说,当middle 在递归调用的第二个“分支”中接近to 时,您将46341 乘以46342,由于溢出(demo)产生-2147432674

    您可以通过仅将 BigInteger 用于“有效负载”乘法来解决此问题,即

    BigInteger.valueOf(middle+1).multiply(BigInteger.valueOf(to))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-02
      • 2020-09-24
      • 2015-12-12
      • 1970-01-01
      • 1970-01-01
      • 2011-07-13
      相关资源
      最近更新 更多