Почему обратная рекурсия выполняется быстрее, чем прямая рекурсия в python

Я сделал алгоритм в Python для подсчета количества способов получения суммы денег с разными номиналами монет:

@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)

Мой алгоритм использует индекс, чтобы получить номинал монеты, и, как вы можете видеть, мой индекс увеличивается в каждом кадре стека, в который я вхожу. Я понял, что алгоритм также можно записать следующим образом:

@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)

На этот раз индекс уменьшает каждый кадр стека, в который я вхожу. Я сравнивал время выполнения функций, и я получил очень примечательную разницу:

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.

Почему существует большая разница во времени выполнения алгоритмов, если я даже не кеширую результаты? Почему возрастающий порядок индекса влияет на это время выполнения?

Ответы

Ответ 1

Это не имеет ничего общего с динамическим программированием, как я понимаю. Простое изменение индексов не должно делать что-то "динамическое".

Что происходит, так это то, что алгоритм чувствителен к входу. Попробуйте подавать вход в обратном порядке. Например,

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

Подобно тому, как некоторые алгоритмы сортировки чрезвычайно быстрые с уже отсортированными данными и очень медленными с обратными данными, у вас есть такой алгоритм здесь.

В случае, когда ввод увеличивается, countChange требуется гораздо больше итераций, чтобы прийти к его окончательному ответу и, таким образом, кажется намного медленнее. Однако, когда вход уменьшается, характеристики производительности меняются на противоположные.

Ответ 2

Число комбинаций чисел не огромно

причина в том, что в будущем вам нужно исследовать каждую возможность, однако, когда вы идете назад, вы можете устранить большие куски недействительных решений, не вычисляя их

Вперед вы вызываете счет 500k раз

Возвращаясь назад, ваш код делает только подсчеты 30 000 для подсчета...

вы можете сделать это быстрее, уведомив вызовы (или изменив алгоритм, чтобы не делать повторяющиеся вызовы)