Рекурсія

Модуль 2. Java Core
Рівень 10 , Лекція 0
Відкрита

— Привіт, Аміго. Сьогодні говоримо про рекурсію.

Рекурсія - 1

Як ти знаєш, у Java одні методи викликають інші методи. При цьому, при виклику методу передаються конкретні значення аргументів, а під час його роботи локальні змінні методи набувають деяких значень.

— Так.

— І, як ти знаєш, внутрішні змінні різних методів незалежні одна від одної.

— Ага.

— Так ось, уяви ситуацію, коли метод може викликати сам себе. Саме вона називається рекурсією. Приклад:

Приклад
public static void main(String[] args)
{
 countDown(10);
}

public static void countDown(int x)
{
 if (x <=0)
  System.out.println("Boom!");
 else
 {
  System.out.println(x);
  countDown(x - 1);
 }
}
Виведення на екран:
10
9
8
7
6
5
4
3
2
1
Boom!

— Чесно кажучи, бачу, що метод у коді сам себе викликає, але не розумію, що саме при цьому відбувається.

— Так приблизно те саме, що й при виклику іншого методу.

— Ні, я питаю, що відбувається зі змінними? З їхніми значеннями? І як ми виходимо із методу? Чи ми виходимо з усіх одразу?

— Та все набагато простіше. Уяви, що метод, який викликає сам себе, розмножили багато разів. Тоді буде аналогічна ситуація:

Рекурсивний виклик методу Що відбувається «насправді»
public static void main(String[] args)
{
 countDown(3);
}

public static void countDown(int x)
{
 if (x <=0)
  System.out.println("Boom!");
 else
 {
  System.out.println(x);
  countDown(x - 1);
 }
}
public static void main(String[] args)
{
 countDown1(3);
}

public static void countDown1(int x)
{
 if (x <=0)
  System.out.println("Boom!");
 else
 {
  System.out.println(x);
  countDown2(x - 1);
 }
}
public static void countDown2(int x)
{
 if (x <=0)
  System.out.println("Boom!");
 else
 {
  System.out.println(x);
  countDown3(x - 1);
 }
}
public static void countDown3(int x)
{
 if (x <=0)
  System.out.println("Boom!");
 else
 {
  System.out.println(x);
  countDown4(x - 1);
 }
}

public static void countDown4(int x)
{
 if (x <=0)
  System.out.println("Boom!");
 else
 {
  System.out.println(x);
  countDown5(x - 1);
 }
}
Виведення на екран: Виведення на екран:
3
2
1
Boom!
3
2
1
Boom!

Тобто кожного разу при виклику методу (навіть самого себе) створюються нові змінні, які зберігають дані для цього методу. Жодних загальних змінних немає.

При кожному виклику у пам'яті з'являється ще одна копія аргументів методу, але вже з новими значеннями. При поверненні в старий метод там використовуються його змінні. Тобто, при рекурсії фактично ми викликаємо інший метод, але з таким же кодом, як в нашому!

— Зрозуміло. А як працює вихід із цих методів? Чи можна приклад?

— Гаразд. Один приклад коштує тисячі слів. Ось тобі приклад:

Рекурсивний виклик методу Що відбувається «насправді»
public static void main(String[] args)
{
 print(3);
}

public static void print(int x)
{
 if (x <=0)
  System.out.println("Boom!");
 else
 {
  System.out.println(x);
  print(x - 1);
  System.out.println(x);
 }
}
public static void main(String[] args)
{
 print1(3);
}

public static void print1(int x)
{
 if (x <=0)
  System.out.println("Boom!");
 else
 {
  System.out.println(x);
  print2(x - 1);
  System.out.println(x);
 }
}

public static void print2(int x)
{
 if (x <=0)
  System.out.println("Boom!");
 else
 {
  System.out.println(x);
  print3(x - 1);
  System.out.println(x);
 }
}

…
Виведення на екран: Виведення на екран:
3
2
1
Boom!
1
2
3
3
2
1
Boom!
1
2
3

— Ок. Ніби зрозумів. А навіщо потрібна рекурсія?

— Є дуже багато завдань, які розбиваються на окремі підзавдання, ідентичні до початкового завдання. Наприклад, треба обійти всі елементи дерева XML. Кожен елемент може мати кілька дочірніх елементів, а вони — свої.

Або тобі потрібно вивести список файлів, які є в директорії та всіх її піддиректоріях. Тоді ти пишеш метод, який виводить файли поточної директорії, а потім для отримання файлів усіх піддиректорій викликаєш його, але з іншим параметром – піддиректорією.

Приклад:

Виведення всіх файлів на екран із директорії та її піддиректорій
public static void main(String[] args)
{
 printAllFiles(new File("c:/windows/"));
}

public static void printAllFiles(File dir)
{
 for (File file : dir.listFiles())
 {
  if (file.isDirectory())
   printAllFiles(file);
  else
   System.out.println(file.getAbsolutePath()); 
 }
}

Рядок 8 – отримуємо список всіх файлів (і директорій) у директорії dir.

Рядки 10-11 – якщо файл насправді директорія, то виведення її файлів знову викликаємо printAllFiles, але вже з іншим параметром – піддиректорією.

Рядок 13 – виводимо ім'я поточного файлу.

— Ок. Начебто зрозуміло.

Коментарі (6)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Іван Рівень 73 Expert
23 вересня 2023
Дурних питань немає ))) Друзі, поясніть чому в прикладі public static void main(String[] args) { print(3); } public static void print(int x) { if (x <=0) System.out.println("Boom!"); else { System.out.println(x); print(x - 1); System.out.println(x); } } Ось такий вивід в консоль: 3 2 1 Boom! 1 2 3 Розумію до Boom! метод викликає сам себе -1, але після 0 спрацьовує остання строка System.out.println(x); і починає додавати... 1 2 3. Хоча там нема ітерації ....
Павло Рівень 48
1 січня 2024
Прогнав дебагом і теж не поняв як воно там змінюється. Запитаю ментора і відпишу
Павло Рівень 48
1 січня 2024
3 2 1 1 2 3
Павло Рівень 48
1 січня 2024
Цей код є рекурсивною функцією, яка виводить числа від заданого значення до 1, а потім в зворотньому порядку від 1 до заданого значення. Ось як це працює: 1. У функції `print` перевіряється, чи `x` менше або дорівнює 0. Якщо це так, виводиться повідомлення "Boom!". 2. Якщо `x` більше 0, то спочатку виводиться поточне значення `x`, потім функція `print` викликається з аргументом `x - 1`. 3. Після рекурсивного виклику функції `print`, знову виводиться поточне значення `x`. Коли рекурсивний виклик досягає базового випадку (коли `x` стає меншим або дорівнює 0), виконання функції повертається назад, і числа виводяться у зворотньому порядку. Якщо запустити цей код з аргументом 3, результат буде наступним: ``` 3 2 1 1 2 3 ``` Це черговий приклад рекурсивної функції, яка використовує саму себе для вирішення проблеми шляхом декомпозиції її на менші підзадачі.
Павло Рівень 48
1 січня 2024
Запитав в Моніки
Павло Рівень 48
1 січня 2024
https://www.youtube.com/watch?v=9Hs7DuIJ3LE&t=17s толково пояснює