Рекурсія - це хитра тема, яку потрібно зрозуміти, і я не думаю, що я можу повністю зробити це справедливим. Натомість я спробую зосередитись на конкретному фрагменті коду, який ви маєте тут, і спробую описати як інтуїцію, чому рішення працює, так і механіку того, як код обчислює його результат.
Код, який ви вказали тут, вирішує таку проблему: ви хочете знати суму всіх цілих чисел від 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, включно (це рекурсивний виклик sumInts), а потім додайте 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 forsumInts (6, 5) , namely 0. It then returns5 + 0 = 5`.
sumInts(4, 5)тепер має значення для sumInts(5, 5), а саме 5. Потім воно повертається 4 + 5 = 9.
Іншими словами, повернене значення формується шляхом підсумовування значень по одному, кожен раз приймаючи одне значення, повернене певним рекурсивним викликом sumIntsі додаючи поточне значення a. Коли рекурсія знижується, найглибший виклик повертається 0. Однак це значення не одразу виходить з ланцюга рекурсивних викликів; натомість він просто повертає значення назад до рекурсивного виклику одного шару над ним. Таким чином, кожен рекурсивний дзвінок просто додає ще одне число і повертає його вище в ланцюжку, що завершується загальним підсумком. Як вправу, спробуйте простежити це, з sumInts(2, 5)чого ви хотіли почати.
Сподіваюся, це допомагає!