If this is your first visit, be sure to
check out the FAQ by clicking the
link above. You may have to register or Login
before you can post: click the register link above to proceed. To start viewing messages,
select the forum that you want to visit from the selection below.

Re: How to estimate the number of recursive calls

Originally Posted by nuzzle

I took just a second on an ordinary desktop. Quite amazing.

Just for fun I've optimized the code in my previous reply.

It's based on two features. First, Pascal's triangle is symmetrical so only one half needs be calculated. Second, the recurrence algorithm works also with a cumulative Pascal's triangle without additional cost. Cumulative means that each number in the triangle is no longer an individual binominal but the sum of all binominals to the left on the same row. This means that not all numbers in the subtriangle needs be summed anymore, only the number at the far right on each row (because it's in effect the row sum).

These changes together speed up the algorithm about 4 times. So f(2000,1000) for example which took about 1 second before now takes 1/4 of a second. To push it a little I tried f(10000,5000). It's an enormous integer with about 3000 digits. It took just 40 seconds.

Re: How to estimate the number of recursive calls

I've just reread the problem statement in post #1 and, unless I'm missing something, there should be an easier solution: you could just identify a recursion branch with a path in the n,k plane. Then, the number of calls equals exactly the number of paths from a starting point (n,k) to the border {(n,k)| n=0 or k=-1} where paths should not touch the k=-1 line more than once, times two minus one ( because of the first call ).

now, the vertical border {(0,j)|j>-1} gives just a sum of binomials : sum{j=0 to k}binomial(n,j)
the horizontal border {(j,-1)|j>=0} gives a sum where each term is the number of all possible paths to (j,-1) minus the (j+1)-term ( due to the fact that paths moving horizontally on the k=-1 line should not be counted ) up to j=n-k-1. Terms cancel term by term leaving just a binomial(n,k+1).

Hence, the number of calls is just 2*( sum{j=0 to k+1} binomial(n,j) ) - 1 that can be computed in linear time by using the usual binomial recursive relations ( I've not checked the result above deeply, but a quick numerical check gives the same results as in post #3 and #15 ). Moreover, a constant time approximation for big n can be quickly obtained by approximating the binomial sum via the error function.

Last edited by superbonzo; December 19th, 2012 at 09:59 AM.
Reason: typos

Re: How to estimate the number of recursive calls

Originally Posted by superbonzo

I've just reread the problem statement in post #1 and, unless I'm missing something, there should be an easier solution: you could just identify a recursion branch with a path in the n,k plane. Then, the number of calls equals exactly the number of paths from a starting point (n,k) to the border {(n,k)| n=0 or k=-1} where paths should not touch the k=-1 line more than once, times two minus one ( because of the first call ).

now, the vertical border {(0,j)|j>-1} gives just a sum of binomials : sum{j=0 to k}binomial(n,j)
the horizontal border {(j,-1)|j>=0} gives a sum where each term is the number of all possible paths to (j,-1) minus the (j+1)-term ( due to the fact that paths moving horizontally on the k=-1 line should not be counted ) up to j=n-k-1. Terms cancel term by term leaving just a binomial(n,k+1).

Hence, the number of calls is just 2*( sum{j=0 to k+1} binomial(n,j) ) - 1 that can be computed in linear time by using the usual binomial recursive relations ( I've not checked the result above deeply, but a quick numerical check gives the same results as in post #3 and #15 ). Moreover, a constant time approximation for big n can be quickly obtained by approximating the binomial sum via the error function.

If you mean binomial the formula that I posted in #1 it is wrong formula for calculate binomial because it always answers 1 to us
the right formula for binomial is

In this subtriangle from rows 3 to 5 the diagonal sums are found on row 6 like this,

15 = 1+4+10
6 = 1+5
1 = 1

So rather than summing all binominals inside the subtriangle one can as well sum fewer on the row below. I can't believe I missed this but I did. Anyway my formula now becomes,

f(n,k) = 2 * (2^n - sum{j=0 to n-k-2} bin(n,j)) - 1

Noting that 2^n is the total sum of all binominals on row n the expression reduces to what you suggest,

f(n,k) = 2 * (sum{j=0 to k+1} bin(n,j)) - 1

Now if the cumulative version of the recurrence algorithm for Pascal's triangle is used the sum in this formula is for free and the complexity for f(n,k) in fact can be considered constant. In practice the binominal calculations and the arbitrary precision arithmetics will make it closer to quadratic though.

Here's the new code. It's somewhat simpler in structure but performance-wise it's as before,

I ran it for f(2000,1000) and f(10000,5000) and they clocked in at 4 and 70 milliseconds respectively. So the C++ version was twice as fast as the Java version. That's not conclusive of course since we are using different computers but I have a feeling this result quite well reflects the true state of affairs. I think it's quite impressive of Java to be that close to C++. Especially considering that BigInteger is immutable so there are plenty of heap allocations taking place behind the scene.