Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does backward recursion execute faster than forward recursion in python

I made an algorithm in Python for counting the number of ways of getting an amount of money with different coin denominations:

@measure
def countChange(n, coin_list):
    maxIndex = len(coin_list)
    def count(n, current_index):        
        if n>0 and maxIndex>current_index:
            c = 0
            current = coin_list[current_index]
            max_coeff = int(n/current)      
            for coeff in range(max_coeff+1):
                c+=count(n-coeff*current, current_index+1)
        elif n==0: return 1
        else: return 0
        return c
    return count(n, 0)

My algorithm uses an index to get a coin denomination and, as you can see, my index is increasing in each stack frame I get in. I realized that the algorithm could be written in this way also:

@measure
def countChange2(n, coin_list):
    maxIndex = len(coin_list)
    def count(n, current_index):        
        if n>0 and 0<=current_index:
            c = 0
            current = coin_list[current_index]
            max_coeff = int(n/current)      
            for coeff in range(max_coeff+1):
                c+=count(n-coeff*current, current_index-1)
        elif n==0: return 1
        else: return 0
        return c
    return count(n, maxIndex-1)

This time, the index is decreasing each stack frame I get in. I compared the execution time of the functions and I got a very noteworthy difference:

print(countChange(30, range(1, 31)))
print(countChange2(30, range(1, 31)))

>> Call to countChange took 0.9956174254208345 secods.
>> Call to countChange2 took 0.037631815734429974 secods.

Why is there a great difference in the execution times of the algorithms if I'm not even caching the results? Why does the increasing order of the index affect this execution time?

like image 318
rfrm Avatar asked May 07 '14 18:05

rfrm


People also ask

Is tail recursion faster?

As a rule of thumb; tail-recursive functions are faster if they don't need to reverse the result before returning it. That's because that requires another iteration over the whole list. Tail-recursive functions are usually faster at reducing lists, like our first example.

What is forward and backward recursion?

Forward recursion involves moving in a direction from the first stage to the last stage. Backward recursion is the opposite, where the problem is solved from the last stage backward to the first stage. Forward recursion is advantageous for problems that involve uncertain time horizons (Kennedy, 1986).

What are the advantages of recursive function in Python?

Advantages of Recursion Recursive functions make the code look clean and elegant. A complex task can be broken down into simpler sub-problems using recursion. Sequence generation is easier with recursion than using some nested iteration.

Is recursion efficient in Python?

Avoid recursive calls Recursive method calls in Python cause a new stack frame allocation for every call. If you can iterate over a list instead then you avoid this allocation and will see a tremendous speed increase. The code below runs around 4x faster as a loop than as a recursive method.


2 Answers

This doesn't really have anything to do with dynamic programming, as I understand it. Just reversing the indices shouldn't make something "dynamic".

What's happening is that the algorithm is input sensitive. Try feeding the input in reversed order. For example,

print(countChange(30, list(reversed(range(1, 31)))))
print(countChange2(30, list(reversed(range(1, 31)))))

Just as some sorting algorithms are extremely fast with already sorted data and very slow with reversed data, you've got that kind of algorithm here.

In the case where the input is increasing, countChange needs a lot more iterations to arrive at its final answer, and thus seems a lot slower. However, when the input is decreasing, the performance characteristics are reversed.

like image 97
John Y Avatar answered Oct 24 '22 06:10

John Y


thre number combinations are not huge

the reason is that going forward you have to explore every possibility, however when you go backwards you can eliminate large chunks of invalid solutions without having to actually calculate them

going forward you call count 500k times

going backwards your code only makes 30k calls to count ...

you can make both of these faster by memoizing the calls , (or changing your algorithm to not make duplicate calls)

like image 37
Joran Beasley Avatar answered Oct 24 '22 05:10

Joran Beasley