Рекурсія - це хитра тема, яку потрібно зрозуміти, і я не думаю, що я можу повністю зробити це справедливим. Натомість я спробую зосередитись на конкретному фрагменті коду, який ви маєте тут, і спробую описати як інтуїцію, чому рішення працює, так і механіку того, як код обчислює його результат.
Код, який ви вказали тут, вирішує таку проблему: ви хочете знати суму всіх цілих чисел від 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, включно, ви можете зробити наступне. Спочатку обчисліть суму цілих чисел між + 1 та b включно. Далі додайте до цього загального значення. Ви помітите, що "обчислити суму цілих чисел між + 1 і b, включно", трапляється майже однакова проблема, яку ми вже намагаємося вирішити, але з дещо іншими параметрами. Замість того, щоб обчислити від a до b, включно, ми обчислюємо від + 1 до b включно. Це рекурсивний крок - щоб вирішити більшу задачу ("сума від a до b, включно"), ми зменшуємо проблему до меншої версії себе ("сума від a + 1 до b, включно.").
Якщо ви подивитесь на вищезазначений код, ви помітите, що в ньому є цей крок:
return a + sumInts(a + 1, b: b)
Цей код є просто перекладом наведеної вище логіки - якщо ви хочете підсумовувати від a до b, включно, почніть з підсумовування а + 1 до b, включно (це рекурсивний виклик sumInt
s), а потім додайте a
.
Звичайно, сам по собі такий підхід насправді не спрацює. Наприклад, як би ви обчислили суму всіх цілих чисел від 5 до 5 включно? Ну, використовуючи нашу нинішню логіку, ви обчислите суму всіх цілих чисел між 6 і 5, включно, а потім додасте 5. Отже, як обчислити суму всіх цілих чисел між 6 і 5 включно? Ну, використовуючи нашу нинішню логіку, ви обчислите суму всіх цілих чисел між 7 і 5 включно, а потім додасте 6. Ви помітите тут проблему - це просто продовжує йти!
При рекурсивному вирішенні проблеми повинен бути якийсь спосіб зупинити спрощення проблеми, а натомість просто вирішити її безпосередньо. Як правило, ви знайдете простий випадок, коли відповідь можна визначити негайно, а потім структуруйте своє рішення для вирішення простих випадків безпосередньо, коли вони виникають. Зазвичай це називається базовим випадком або рекурсивною базою .
Отже, що є базовим у цій конкретній проблемі? Коли ви підсумовуєте цілі числа від a до b, включно, якщо випадково більший за 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. В іншому випадку обчисліть суму між а + 1 і b, а потім перейдіть, додайте а.
Поки що я дав лише ідею високого рівня за кодом. Але у вас було ще два, дуже хороших питання. По-перше, чому це не завжди повертає 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)
. Виклик до 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)
чого ви хотіли почати.
Сподіваюся, це допомагає!