Рекурсия

Открыта

— Привет, Амиго. Сегодня Билаабо расскажет тебе, что такое рекурсия.

Рекурсия - 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 – выводим имя текущего файла.

— Ок. Вроде понял. Спасибо, Билаабо.

Комментарии (5)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Anonymous #3613348
Уровень 112
25 декабря 2025, 08:31
Рекурсия в Java (и программировании в целом) – это техника, когда функция вызывает саму себя для решения более мелкой версии той же задачи. Вот ключевые аспекты рекурсии: Базовый случай (Base Case): Условие, при котором функция перестает вызывать саму себя и возвращает конкретное значение. Без базового случая рекурсивная функция будет вызывать себя бесконечно, что приведет к переполнению стека (StackOverflowError). Базовый случай – это точка остановки рекурсии. Рекурсивный вызов (Recursive Call): Внутри функции происходит вызов самой себя, но с измененными аргументами. Эти измененные аргументы должны приближать проблему к базовому случаю. Как это работает: Когда функция вызывается рекурсивно, в памяти (в стеке вызовов) создается новый "фрейм" для этой функции, содержащий ее локальные переменные и параметры. Этот новый фрейм "ложится" поверх предыдущего фрейма. Когда базовый случай достигнут, рекурсивный вызов прекращается, и функция возвращает значение. Возвращенное значение передается в предыдущий фрейм, который продолжает вычисления. Этот процесс продолжается, пока все фреймы не будут обработаны и не будет возвращено итоговое значение.
Константин
Уровень 81
8 сентября 2024, 07:19
Я один читаю Бильбао вместо Билаабо?
12 октября 2023, 09:12
Билаабо, что за токсичный тон!? "Господи. Да все гораздо проще."
Anton Petrov
Уровень 39
28 июня, 04:42
Если вам важно сохранить рабочие отношения, проигнорируйте эмоции и сфокусируйтесь на сути (на коде). «Отлично, покажи свой вариант решения. Если он оптимальнее, с удовольствием разберу / поправлю код». «Круто, если есть более простое решение! Поделись кодом (или пулл-реквестом), посмотрим на бенчмарки / читаемость». «Буду рад увидеть твою реализацию. Всегда за то, чтобы сделать код чище».
Антон
Уровень 108
Expert
19 мая 2022, 20:47
Судя по размеру лекции, не похоже на Билаабо. Скорее уж Элли)