Рекурсия - непростая тема для понимания, и я не думаю, что смогу в полной мере передать ее здесь. Вместо этого я попытаюсь сосредоточиться на конкретном фрагменте кода, который у вас есть, и попытаться описать как интуитивное понимание того, почему решение работает, так и механизм вычисления результата кодом.
Приведенный здесь код решает следующую проблему: вы хотите узнать сумму всех целых чисел от a до b включительно. В вашем примере вам нужна сумма чисел от 2 до 5 включительно, т.е.
2 + 3 + 4 + 5
При попытке рекурсивного решения проблемы одним из первых шагов должно быть выяснение того, как разбить проблему на меньшую проблему с той же структурой. Предположим, вы хотите просуммировать числа от 2 до 5 включительно. Один из способов упростить это - заметить, что указанную выше сумму можно переписать как
2 + (3 + 4 + 5)
Здесь (3 + 4 + 5) оказывается суммой всех целых чисел от 3 до 5 включительно. Другими словами, если вы хотите узнать сумму всех целых чисел от 2 до 5, начните с вычисления суммы всех целых чисел от 3 до 5, а затем добавьте 2.
Так как же вычислить сумму всех целых чисел от 3 до 5 включительно? Что ж, эта сумма
3 + 4 + 5
который можно рассматривать как
3 + (4 + 5)
Здесь (4 + 5) - это сумма всех целых чисел от 4 до 5 включительно. Итак, если вы хотите вычислить сумму всех чисел от 3 до 5 включительно, вы должны вычислить сумму всех целых чисел от 4 до 5, а затем добавить 3.
Здесь есть шаблон! Если вы хотите вычислить сумму целых чисел от a до b включительно, вы можете сделать следующее. Сначала вычислите сумму целых чисел от a + 1 до b включительно. Затем добавьте к этой сумме. Вы заметите, что «вычислить сумму целых чисел от a + 1 до b включительно» - это примерно та же проблема, которую мы уже пытаемся решить, но с немного другими параметрами. Вместо того, чтобы вычислять от a до b включительно, мы вычисляем от a + 1 до b включительно. Это рекурсивный шаг - чтобы решить большую проблему («сумма от a до b, включительно»), мы уменьшаем проблему до меньшей версии самой себя («сумма от a + 1 до b, включительно»).
Если вы посмотрите на приведенный выше код, вы заметите, что в нем есть следующий шаг:
return a + sumInts(a + 1, b: b)
Этот код является просто переводом вышеупомянутой логики - если вы хотите суммировать от a до b включительно, начните с суммирования a + 1 до b включительно (это рекурсивный вызов sumInt
s), а затем добавьте a
.
Конечно, сам по себе такой подход не работает. Например, как бы вы вычислили сумму всех целых чисел от 5 до 5 включительно? Итак, используя нашу текущую логику, вы бы вычислили сумму всех целых чисел от 6 до 5 включительно, а затем прибавили бы 5. Итак, как вы вычислите сумму всех целых чисел от 6 до 5 включительно? Итак, используя нашу текущую логику, вы должны вычислить сумму всех целых чисел от 7 до 5 включительно, а затем добавить 6. Вы заметите здесь проблему - это просто продолжается и продолжается!
При рекурсивном решении проблемы должен быть какой-то способ перестать упрощать проблему и вместо этого просто решить ее напрямую. Как правило, вы найдете простой случай, когда ответ может быть определен немедленно, а затем структурируете свое решение для решения простых случаев сразу же, когда они возникают. Обычно это называется базовым случаем или рекурсивным базисом .
Итак, каков базовый случай в этой конкретной проблеме? Когда вы суммируете целые числа от a до b включительно, если a оказывается больше b, то ответ равен 0 - в этом диапазоне нет чисел! Поэтому мы структурируем наше решение следующим образом:
- Если a> b, то ответ - 0.
- В противном случае (a ≤ b) получите ответ следующим образом:
- Вычислите сумму целых чисел от a + 1 до b.
- Добавьте, чтобы получить ответ.
Теперь сравните этот псевдокод с вашим реальным кодом:
func sumInts(a: Int, b: Int) -> Int {
if (a > b) {
return 0
} else {
return a + sumInts(a + 1, b: b)
}
}
Обратите внимание, что между решением, описанным в псевдокоде, и настоящим кодом существует почти однозначное соответствие. Первый шаг - это базовый случай - в случае, если вы запрашиваете сумму для пустого диапазона чисел, вы получаете 0. В противном случае вычислите сумму между a + 1 и b, затем добавьте a.
Пока что я дал лишь общее представление о коде. Но у вас было два других очень хороших вопроса. Во-первых, почему это не всегда возвращает 0, учитывая, что функция требует вернуть 0, если a> b? Во-вторых, откуда на самом деле 14? Давайте посмотрим на них по очереди.
Давайте попробуем очень и очень простой случай. Что будет, если вы позвоните sumInts(6, 5)
? В этом случае, прослеживая код, вы видите, что функция просто возвращает 0. Это правильно, чтобы - в диапазоне не было никаких чисел. А теперь попробуйте что-нибудь посложнее. Что происходит, когда вы звоните sumInts(5, 5)
? Что ж, вот что происходит:
- Вы звоните
sumInts(5, 5)
. Мы попадаем вelse
Попадаем ветку, которая возвращает значение `a + sumInts (6, 5).
- Чтобы
sumInts(5, 5)
определить, что sumInts(6, 5)
есть, нам нужно приостановить то, что мы делаем, и позвонить sumInts(6, 5)
.
sumInts(6, 5)
называется. Он входит в if
филиал и возвращается 0
. Однако этот экземпляр sumInts
был вызван пользователем sumInts(5, 5)
, поэтому возвращаемое значение передается обратно sumInts(5, 5)
, а не вызывающей стороне верхнего уровня.
sumInts(5, 5)
теперь можно вычислить, 5 + sumInts(6, 5)
чтобы вернуться 5
. Затем он возвращает его вызывающей стороне верхнего уровня.
Обратите внимание, как здесь сформировалось значение 5. Мы начали с одного активного звонка в sumInts
. Это запустило еще один рекурсивный вызов, и значение, возвращенное этим вызовом, передало информацию обратно sumInts(5, 5)
. Вызов to, sumInts(5, 5)
в свою очередь, произвел некоторые вычисления и вернул значение вызывающей стороне.
Если вы попробуете это сделать sumInts(4, 5)
, вот что произойдет:
sumInts(4, 5)
пытается вернуться 4 + sumInts(5, 5)
. Для этого он звонит sumInts(5, 5)
.
sumInts(5, 5)
пытается вернуться 5 + sumInts(6, 5)
. Для этого он звонит sumInts(6, 5)
.
sumInts(6, 5)
возвращает 0 обратно в sumInts(5, 5).</li>
<li>
sumInts (5, 5) now has a value for
sumInts (6, 5) , namely 0. It then returns
5 + 0 = 5`.
sumInts(4, 5)
теперь имеет значение sumInts(5, 5)
, а именно 5. Затем он возвращается 4 + 5 = 9
.
Другими словами, возвращаемое значение формируется путем суммирования значений по одному, каждый раз беря одно значение, возвращаемое конкретным рекурсивным вызовом, sumInts
и добавляя текущее значение a
. Когда рекурсия достигает дна, самый глубокий вызов возвращает 0. Однако это значение не сразу выходит из цепочки рекурсивных вызовов; вместо этого он просто возвращает значение рекурсивному вызову на один уровень выше. Таким образом, каждый рекурсивный вызов просто добавляет еще одно число и возвращает его выше в цепочке, что завершается общим суммированием. В качестве упражнения попробуйте отследить это дляsumInts(2, 5)
, с чего вы и хотели начать.
Надеюсь это поможет!