Рекурсивная комбинация Python

Как я могу написать функцию, которая вычисляет:

C(n,k)= 1                          if k=0
        0                          if n<k
        C(n-1,k-1)+C(n-1,k)     otherwise

Пока что у меня есть:

def choose(n,k):
if k==0:
   return 1
elif n<k:
   return 0
else:

5 ответов

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

def choose(n,k):
  if k==0:
     return 1
  elif n<k:
     return 0
  else:
     return choose(n-1, k-1) + choose(n-1, k)

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

Вот пример итеративной функции, которая использует стек для имитации рекурсии, избегая при этом максимального предела рекурсии Python:

def choose_iterative(n, k):
  stack = []
  stack.append((n, k))
  combinations = 0

  while len(stack):
    n, k = stack.pop()
    if k == 0:
      combinations += 1
    elif n<k:
      combinations += 0 #or just replace this line with `pass`
    else:
      stack.append((n-1, k))
      stack.append((n-1, k-1))

  return combinations    

Решение из Википедии ( http://en.wikipedia.org/wiki/Binomial_coefficient)

def choose(n, k):
    if k < 0 or k > n:
        return 0
    if k > n - k: # take advantage of symmetry
        k = n - k
    if k == 0 or n <= 1:
        return 1
    return choose(n-1, k) + choose(n-1, k-1)

Улучшение от экспоненциального до линейного времени

Все ответы, приведенные до сих пор, показывают экспоненциальное время. O(2n), Тем не менее, это можно сделать в O(k) изменив одну строку кода.


Объяснение:

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

def choose(n, k):
    ...
    return choose(n-1, k-1) + choose(n-1, k)

Чтобы понять, почему это так плохо, рассмотрим пример choose(500, 2), Числовое значение 500 choose 2 является 500*499/2; однако, используя рекурсию выше, требуется 250499 рекурсивные вызовы, чтобы вычислить это. Очевидно, что это излишне, так как требуется всего 3 операции.

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

Например, следующая рекурсия эквивалентна, но использует только 3 рекурсивные вызовы для вычисления choose(500,2) ( см. Ideone здесь):

def choose(n,k):
    ...
    return ((n + 1 - k)/k)*choose(n, k-1)

Причина улучшения заключается в том, что каждая рекурсия имеет только одну подзадачу, которая уменьшает k от 1 с каждым звонком. Это означает, что мы гарантируем, что эта рекурсия займет только k + 1 рекурсии или O(k), Это огромное улучшение для изменения одной строки кода!

Если вы хотите сделать еще один шаг вперед, вы можете воспользоваться симметрией в "n выберите k", чтобы убедиться, что k <= n/2 ( см. Ideone здесь):

def choose(n,k):
    ...
    k = k if k <= n/2 else n - k            # if k > n/2 it's equivalent to k - n
    return ((n + 1 - k)/k)*choose(n, k-1)

Вы пытаетесь вычислить количество вариантов, чтобы выбрать k из n элементов:

def choose(n,k):
    if k == 0: 
       return 1 # there's only one option to choose zero items out of n
    elif n < k:
       return 0 # there's no way to choose k of n when k > n
    else:
        # The recursion: you can do either
        # 1. choose the n-th element and then the rest k-1 out of n-1
        # 2. or choose all the k elements out of n-1 (not choose the n-th element)
        return choose(n-1, k-1) + choose(n-1, k)

Именно так

def choose(n,k):
    if k==0:
        return 1
    elif n<k:
        return 0
    else:
        return choose(n-1,k-1)+choose(n-1,k)

РЕДАКТИРОВАТЬ

Это простой способ, для эффективного взглянуть на wikipedia и spencerlyon2 answer

Другие вопросы по тегам