3

In continuum to this question:

I have a function in c++ that calls itself over and over again. This is it:

#include <iostream>
#include <math.h>
#include <stdio.h>
#include <cmath>
using namespace std;
double g( double a, double x){
    if (x>=a) return (g(a,x-1)+g(a,x-a));
    else if (x<a) return 1;
    return 0; //Never Reached
}
int main(){
    cout << (unsigned long)g(sqrt(90),90) <<endl; // outputs 7564511
    cout << (unsigned long)g(sqrt(10000019),10000019)<<endl; // Signal: SIGSEGV (Segmentation fault)
}

I would like to know how this function can be converted into some sort of iteration or tail loop (or anything that stops the segfault), but more importantly I need to know how to actually do it yourself.


NOTE: I apologize in advance if this is a trivial question.

NOTE2: There are similar questions (like this or this), but none of the ones I found address the fact that my function calls itself twice each iteration.

Community
  • 1
  • 1
Kaiden Prince
  • 472
  • 3
  • 18
  • 1
    this might help you: http://www.codeproject.com/Articles/418776/How-to-replace-recursive-functions-using-stack-and – NathanOliver Oct 08 '15 at 16:24
  • 1
    When a recursive function calls itself twice in the tail call, I don't think it can be converted to a tail recursive function. – R Sahu Oct 08 '15 at 16:24
  • Like @RSahu said, it's impossible. – nouney Oct 08 '15 at 16:35
  • not true. Depends on the nature of the function, for example you make fibonacci be tail recursive http://stackoverflow.com/questions/22111252/tail-recursion-fibonacci – MK. Oct 08 '15 at 16:35
  • 3
    @RSahu Absolutely everything can be converted to a tail recursive function, google "continuation passing style". Whether it will help any is a different question altogether. – n. m. could be an AI Oct 08 '15 at 16:37
  • @MK the logic of the function has to be changed to make it a tail recursive function. In the example code you linked to, the helper function calls itself only once in the tail call. – R Sahu Oct 08 '15 at 16:40
  • well obviously something will have to change. – MK. Oct 08 '15 at 16:42
  • @n.m., I agree that you can convert it to a tail call but the logic of the function has to be re-thought so that the function calls itself only once in the tail call. If you are not able to do that, then the function cannot be tail recursive. – R Sahu Oct 08 '15 at 16:42
  • A modern C/C++ compiler while optimizing will do such restructuring behind the scenes. At least GCC and clang do, if feasible. – vonbrand Oct 09 '15 at 20:09

3 Answers3

3

Memorization can be an effective way to limit the number of recursive calls required for your computation. Try evaluating the function for some simple inputs, like g(2, 8) and you'll see that you end up evaluating the function for the same values over and over. By caching the result for each set of inputs the first time you calculate it, you can short circuit the recursion and dramatically reduce the size of the problem.

To make the function iterative rather than recursive, one strategy you can use is to try to turn the definition around and iterate from the bottom up. Consider the Fibonacci function:

fib(n) = fib(n-1) + fib(n-2)

To calculate fib(n) iteratively, we start from the base cases fib(1) + fib(0) and iterate up to fib(n). That lets you accumulate the value as you go, rather than having to remember where you were (over and over and over again) while you calculate intermediate values. So an iterative definition of fib() looks like:

fib(n) {
    a = 1;
    b = 0;
    fib = 0;
    i = 1;
    while (i < n) {
        fib = a + b;
        b = a;
        a = fib;
        i++;
    }
    return fib;
}

You should be able to do something similar with your g() function. I don't have time to play with it, but I bet that if you try evaluating it by hand for a few a, x pairs you'll notice a pattern that'll let you rewrite the function in an iterative form the way I did above for fib().

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • 2
    yeah, this thing begs for memoization, but it is not what he is asking. – MK. Oct 08 '15 at 17:02
  • @MK. I mentioned memoization because the OP asked for "anything that stops the segfault," and memoization is a big step in the right direction in that respect. – Caleb Oct 08 '15 at 17:07
  • well, naive memoization is not going to cut it -- it stills goes from the large input to small input with step 1. I think we need to invert the order to go from small to large and memoize. – MK. Oct 08 '15 at 17:27
  • 1
    actually i tried it and my initial assessment of memoization was wrong. Because numbers are fractional, the number of hits is not enough to make this reasonable. – MK. Oct 08 '15 at 18:02
1

As many person have already stated, this cannot be directly converted to a tail recursive or an iterative function as floating point function arguments make it difficult to build the result iteratively. However, with a bit of thought the function can be calculated quite efficiently.

First, because all the recursions are summed and end with 1, the function basically calculates the number of paths to the recursion end. For example, for g(5,2), one path would be g(2,5)-> g(2,3) -> g(2,1) (returns 1 here) and an other path g(5,2)-> g(4,2) -> g(3,2) -> g(2,2) -> g(0,2). So to calculate g we need to just calculate the number of the possible paths.

Let's start with the path where we subtract always a from x. Clearly we have only exactly one this kind of path. Next, consider the case where we once we choose the path where we subtract 1 and other times we subtract a, we have floor((x-a)/a) positions to select the 1. Hence, there is floor((x-a)/a) possible paths in this case. In a next iteration, we want to select step 1 twice. There are n*(n-1)/2 combination, where n=floor((x-1-a)/a) and n*(n-1)/2 is the binomial coefficient \binom{n,2} . Next step with three ones there are \binom{n,3} combinations where n is now=floor((x-2-a)/a) etc.

If you pre-calculate the binomial coefficient the algorithm is O(x), hence it probably could also calculate g(sqrt(10000019),10000019). However, the largest native c++ integer type (unsigned long long) overflows already around g(sqrt(500),500). You can use long double to get an approximate value for slightly larger input. Alternatively you can use boost Multiprecision Library to get more digits, but I assume you will run out of memory before you get the answer to g(sqrt(10000019),10000019).

The source code with an overflow check to calculate g()

#include <iostream>
#include <vector>
#include <limits>
#include <algorithm>
#include <cstdlib>

unsigned long long binomial(unsigned int n, unsigned int m) {
  if (n - m < m) m = n - m;
  std::vector<unsigned long long>bc(m+1, 0);
  bc[0] = 1;
  for (unsigned int i = 0;i <= n;++i) {
    for (unsigned int j = std::min(i, m);j > 0;j--) {
      if (std::numeric_limits<unsigned long long>::max()-bc[j-1] < bc[j]) {
        std::cout << "Error: too large binomial coefficient(" << n << "," << m << ")" << std::endl;
        exit(1);
      }
      bc[j] += bc[j - 1];
    }   
  }
  return bc[m];
}

unsigned long long g(double a, double x) {
  unsigned long long r = 1;
  int n = 0;
  for (int i = static_cast<int>(x);i >= a;--i) {
    ++n;
    int m = static_cast<int>((i - a) / a);
    unsigned long long b = binomial(m + n, n);
    if (std::numeric_limits<unsigned long long>::max() - b < r) {
      std::cout << "Error: too large sum " << b << "+" << r << std::endl;
      exit(1);
    }
    r += b;
  }
  return r;
}

int main(){
  std::cout << g(sqrt(90), 90) << std::endl;
  std::cout << g(sqrt(10000019), 10000019) << std::endl;
}
Ari Hietanen
  • 1,749
  • 13
  • 15
0

I guess just for the reference, here's implementation w/o recursion. I am not sure it will ever finish with the given inputs though:

#include <stack>
double gnr(double a, double x) {
   std::stack<double> stack;
   double result = 0;
   stack.push(x);
   while (!stack.empty()) {
      x = stack.top();
      stack.pop();
      //cout << stack.size() << " " << x << endl;
      if (x < a) {
         result++;
      } else {
         stack.push(x - 1);
         stack.push(x - a);
      }
   }
   return result;
}
MK.
  • 33,605
  • 18
  • 74
  • 111