Можете ли вы объяснить мне этот рекурсивный код" N choose k"?
вот код проблемы подмножества с аргументами n и k. n представляет общее количество студентов, а k представляет количество студентов, которых я хочу получить из n. Код пытается дать количество возможных комбинаций вытягивания k числа студентов из n числа студентов.
def subset(n, k):
if k == 0:
return 1
if n == k:
return 1
else:
return subset(n-1, k-1) + subset(n-1, k)
Я понимаю первую часть рекурсивного вызова, но у меня возникли проблемы с пониманием части + subset(n-1, k). Кто-нибудь может мне это объяснить?
3 ответов
всякий раз, когда вы выбираете k
элементы n
есть два случая:
- вы выбираете элемент
#n
- вы не выбираете элемент
#n
поскольку эти события являются взаимоисключающими, общее количество комбинаций дается по количеству комбинаций при выборе #n
, и те, когда вы не выбираете #n
.
выбор элемента #n
поскольку мы уже выбрали один элемент, нам нужно только выбрать другой k-1
элементы. Кроме того, поскольку мы уже определились с одним элементом – включен он или нет – нам нужно рассмотреть только оставшийся n-1
элементы.
таким образом, количество комбинаций для выбора элемента #n
дана by
subset(n - 1, k - 1)
не выбрав элемент #n
есть еще k
элементы на выбор, но так как мы уже приняли решение об элементе #n
остаются n - 1
элементы на выбор. Таким образом:
subset(n - 1, k)
базовый
рекурсия использует тот факт, что обычно мы можем различать две ситуации, где элемент n
является частью этого решения, и те, где это не.
однако такое различие не всегда можно сделать:
- при выборе всех элементов (соответствующих регистру
n == k
в коде ниже) - или при выборе элементов вообще нет (соответствует случаю
k == 0
в коде ниже)
в этих случаях существует только одно решение, следовательно
if k == 0:
return 1
if n == k:
return 1
убедившись, что она работает
для этого нам нужно убедить себя (или доказать), что базовый случай всегда попадает в какой-то момент.
предположим, что n < k
в какой-то момент. Так как по нашему предположению,n
первоначально было больше или равно k
, есть должны был какой-то момент, когда n = k
, потому что n
и k
снижение в унисон или только n
уменьшается на единицу, т. е. отсюда следует
это означает, что должен был быть вызов subset(n - 1, k)
чтобы это случилось, что n
снижается ниже k
. Однако, это невозможно, так как у нас есть базовый случай на n = k
где мы возвращаем постоянным 1
.
Мы заключаем, что либо n
уменьшается в какой-то момент такой, что n = k
, или уменьшение в унисон точно k
времена такие, что k = 0
.
таким образом, в базовом варианте работает.
это скорее математическая проблема, а не вопрос программирования. То, что вы делаете, это вычисление биномиального коэффициента, формула
(n, k) = (n-1, k-1) + (n-1, k) with all (n, 0) and (n, n) having a value of 1.
см. здесь для полного объяснения. Рекурсивное решение можно увидеть здесь. В случае, если указанная ссылка недействительна, просто найдите ее с помощью Google. Я сомневаюсь, что вы получите лучшее объяснение, чем после прочтения этой статьи в Википедии.
фрагмент кода:
if k == 0:
return 1
if n == k:
return 1
использует два комбинаторных факта:
nC0 = 1
nCn = 1
как дела базы.
Далее, после рекурсивного вызова:
return subset(n-1, k-1) + subset(n-1, k)
непосредственно приводит следующий факт в код:
nCr = (n-1)C(k-1) + (n-1)Ck
если у вас есть трудности в понимании того, как выше уравнение выполняется, то читайте дальше: В nCr мы выбираем R элементов из n. Выбор может быть классифицирован на два взаимоисключающих класса, если мы рассмотрим какой-либо конкретный пункт:
-
элемент происходит среди R выбранного элемента
в этом случае мы должны выбрать оставшиеся элементы r-1 из оставшихся элементов n-1. Это можно сделать в (n-1) C(k-1).
-
элемент не встречается среди R выбранного элемента
в этом случае мы должны выбрать все R элементов из оставшихся N-1 элементов, которые могут быть сделаны в (n-1)CK способами.
мы складываем эти два, как я уже сказано, что эти два класса взаимоисключающие и, следовательно, вместе образуют общие способы выбора R элементов из n.