--- comments: true --- # 2.2 Итерация и рекурсия В алгоритмах часто требуется повторное выполнение определенной задачи, что тесно связано с анализом сложности. Поэтому, прежде чем перейти к обсуждению временной и пространственной сложности, рассмотрим, как реализовать повторное выполнение задач в программе, а именно две основные структуры управления программой: итерацию и рекурсию. ## 2.2.1 Итерация Итерация (iteration) - это структура управления, которая позволяет повторно выполнять определенную задачу. В итерации программа повторяет выполнение определенного участка кода, пока выполняется определенное условие. ### 1. Цикл for Цикл `for` - одна из наиболее распространенных форм итерации, **которая подходит для использования, когда количество итераций известно заранее**. Следующая функция реализует суммирование $1 + 2 + \dots + n$ с использованием цикла `for` , а результат суммирования сохраняется в переменной `res` . Следует отметить, что в Python диапазон `range(a, b)` соответствует левому закрытому, правому открытому интервалу, то есть перебираются значения $a, a + 1, \dots, b-1$ : === "Python" ```python title="iteration.py" def for_loop(n: int) -> int: """Цикл for""" res = 0 # Циклическое суммирование 1, 2, ..., n-1, n for i in range(1, n + 1): res += i return res ``` === "C++" ```cpp title="iteration.cpp" /* Цикл for */ int forLoop(int n) { int res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (int i = 1; i <= n; ++i) { res += i; } return res; } ``` === "Java" ```java title="iteration.java" /* Цикл for */ int forLoop(int n) { int res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } ``` === "C#" ```csharp title="iteration.cs" /* Цикл for */ int ForLoop(int n) { int res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } ``` === "Go" ```go title="iteration.go" /* Цикл for */ func forLoop(n int) int { res := 0 // Циклическое суммирование 1, 2, ..., n-1, n for i := 1; i <= n; i++ { res += i } return res } ``` === "Swift" ```swift title="iteration.swift" /* Цикл for */ func forLoop(n: Int) -> Int { var res = 0 // Циклическое суммирование 1, 2, ..., n-1, n for i in 1 ... n { res += i } return res } ``` === "JS" ```javascript title="iteration.js" /* Цикл for */ function forLoop(n) { let res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { res += i; } return res; } ``` === "TS" ```typescript title="iteration.ts" /* Цикл for */ function forLoop(n: number): number { let res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { res += i; } return res; } ``` === "Dart" ```dart title="iteration.dart" /* Цикл for */ int forLoop(int n) { int res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } ``` === "Rust" ```rust title="iteration.rs" /* Цикл for */ fn for_loop(n: i32) -> i32 { let mut res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for i in 1..=n { res += i; } res } ``` === "C" ```c title="iteration.c" /* Цикл for */ int forLoop(int n) { int res = 0; // Циклическое суммирование 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { res += i; } return res; } ``` === "Kotlin" ```kotlin title="iteration.kt" /* Цикл for */ fun forLoop(n: Int): Int { var res = 0 // Циклическое суммирование 1, 2, ..., n-1, n for (i in 1..n) { res += i } return res } ``` === "Ruby" ```ruby title="iteration.rb" ### Цикл for ### def for_loop(n) res = 0 # Циклическое суммирование 1, 2, ..., n-1, n for i in 1..n res += i end res end ``` ??? pythontutor "Визуализация кода"
На рисунке 2-1 представлена блок-схема этой функции суммирования. { class="animation-figure" }Рисунок 2-1 Блок-схема функции суммирования
Количество операций этой функции суммирования пропорционально размеру входных данных $n$ , или, другими словами, линейно зависит от него. **На самом деле временная сложность описывает именно эту линейную зависимость**. Соответствующий материал будет подробно рассмотрен в следующем разделе. ### 2. Цикл while Подобно циклу `for` , цикл `while` также представляет собой метод реализации итерации. В цикле `while` программа перед каждой итерацией проверяет условие: если условие истинно, то выполнение продолжается, иначе цикл завершается. Ниже приведен пример реализации суммирования $1 + 2 + \dots + n$ с использованием цикла `while` : === "Python" ```python title="iteration.py" def while_loop(n: int) -> int: """Цикл while""" res = 0 i = 1 # Инициализация условной переменной # Циклическое суммирование 1, 2, ..., n-1, n while i <= n: res += i i += 1 # Обновить условную переменную return res ``` === "C++" ```cpp title="iteration.cpp" /* Цикл while */ int whileLoop(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Обновить условную переменную } return res; } ``` === "Java" ```java title="iteration.java" /* Цикл while */ int whileLoop(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Обновить условную переменную } return res; } ``` === "C#" ```csharp title="iteration.cs" /* Цикл while */ int WhileLoop(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i; i += 1; // Обновить условную переменную } return res; } ``` === "Go" ```go title="iteration.go" /* Цикл while */ func whileLoop(n int) int { res := 0 // Инициализация условной переменной i := 1 // Циклическое суммирование 1, 2, ..., n-1, n for i <= n { res += i // Обновить условную переменную i++ } return res } ``` === "Swift" ```swift title="iteration.swift" /* Цикл while */ func whileLoop(n: Int) -> Int { var res = 0 var i = 1 // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while i <= n { res += i i += 1 // Обновить условную переменную } return res } ``` === "JS" ```javascript title="iteration.js" /* Цикл while */ function whileLoop(n) { let res = 0; let i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Обновить условную переменную } return res; } ``` === "TS" ```typescript title="iteration.ts" /* Цикл while */ function whileLoop(n: number): number { let res = 0; let i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Обновить условную переменную } return res; } ``` === "Dart" ```dart title="iteration.dart" /* Цикл while */ int whileLoop(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Обновить условную переменную } return res; } ``` === "Rust" ```rust title="iteration.rs" /* Цикл while */ fn while_loop(n: i32) -> i32 { let mut res = 0; let mut i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while i <= n { res += i; i += 1; // Обновить условную переменную } res } ``` === "C" ```c title="iteration.c" /* Цикл while */ int whileLoop(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i; i++; // Обновить условную переменную } return res; } ``` === "Kotlin" ```kotlin title="iteration.kt" /* Цикл while */ fun whileLoop(n: Int): Int { var res = 0 var i = 1 // Инициализация условной переменной // Циклическое суммирование 1, 2, ..., n-1, n while (i <= n) { res += i i++ // Обновить условную переменную } return res } ``` === "Ruby" ```ruby title="iteration.rb" ### Цикл while ### def while_loop(n) res = 0 i = 1 # Инициализация условной переменной # Циклическое суммирование 1, 2, ..., n-1, n while i <= n res += i i += 1 # Обновить условную переменную end res end ``` ??? pythontutor "Визуализация кода" **Цикл `while` обладает большей степенью свободы по сравнению с циклом `for` **. В цикле `while` можно свободно управлять инициализацией и обновлением условной переменной. Например, в следующем коде условная переменная $i$ обновляется дважды на каждой итерации, что затруднительно сделать с использованием цикла `for` : === "Python" ```python title="iteration.py" def while_loop_ii(n: int) -> int: """Цикл while (двойное обновление)""" res = 0 i = 1 # Инициализация условной переменной # Циклическое суммирование 1, 4, 10, ... while i <= n: res += i # Обновить условную переменную i += 1 i *= 2 return res ``` === "C++" ```cpp title="iteration.cpp" /* Цикл while (двойное обновление) */ int whileLoopII(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i++; i *= 2; } return res; } ``` === "Java" ```java title="iteration.java" /* Цикл while (двойное обновление) */ int whileLoopII(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i++; i *= 2; } return res; } ``` === "C#" ```csharp title="iteration.cs" /* Цикл while (двойное обновление) */ int WhileLoopII(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i += 1; i *= 2; } return res; } ``` === "Go" ```go title="iteration.go" /* Цикл while (двойное обновление) */ func whileLoopII(n int) int { res := 0 // Инициализация условной переменной i := 1 // Циклическое суммирование 1, 4, 10, ... for i <= n { res += i // Обновить условную переменную i++ i *= 2 } return res } ``` === "Swift" ```swift title="iteration.swift" /* Цикл while (двойное обновление) */ func whileLoopII(n: Int) -> Int { var res = 0 var i = 1 // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while i <= n { res += i // Обновить условную переменную i += 1 i *= 2 } return res } ``` === "JS" ```javascript title="iteration.js" /* Цикл while (двойное обновление) */ function whileLoopII(n) { let res = 0; let i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i++; i *= 2; } return res; } ``` === "TS" ```typescript title="iteration.ts" /* Цикл while (двойное обновление) */ function whileLoopII(n: number): number { let res = 0; let i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i++; i *= 2; } return res; } ``` === "Dart" ```dart title="iteration.dart" /* Цикл while (двойное обновление) */ int whileLoopII(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i++; i *= 2; } return res; } ``` === "Rust" ```rust title="iteration.rs" /* Цикл while (двойное обновление) */ fn while_loop_ii(n: i32) -> i32 { let mut res = 0; let mut i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while i <= n { res += i; // Обновить условную переменную i += 1; i *= 2; } res } ``` === "C" ```c title="iteration.c" /* Цикл while (двойное обновление) */ int whileLoopII(int n) { int res = 0; int i = 1; // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i; // Обновить условную переменную i++; i *= 2; } return res; } ``` === "Kotlin" ```kotlin title="iteration.kt" /* Цикл while (двойное обновление) */ fun whileLoopII(n: Int): Int { var res = 0 var i = 1 // Инициализация условной переменной // Циклическое суммирование 1, 4, 10, ... while (i <= n) { res += i // Обновить условную переменную i++ i *= 2 } return res } ``` === "Ruby" ```ruby title="iteration.rb" ### Цикл while ### def while_loop(n) res = 0 i = 1 # Инициализация условной переменной # Циклическое суммирование 1, 2, ..., n-1, n while i <= n res += i i += 1 # Обновить условную переменную end res end # ## Цикл while (двойное обновление) ### def while_loop_ii(n) res = 0 i = 1 # Инициализация условной переменной # Циклическое суммирование 1, 4, 10, ... while i <= n res += i # Обновить условную переменную i += 1 i *= 2 end res end ``` ??? pythontutor "Визуализация кода" В целом **код с использованием цикла `for` более компактный, а цикл `while` более гибкий**. Но они оба могут реализовать итерационную структуру. Выбор между ними определяется требованиями конкретной задачи. ### 3. Вложенные циклы Внутрь одной циклической структуры можно вложить другую, например используя два цикла `for` : === "Python" ```python title="iteration.py" def nested_for_loop(n: int) -> str: """Двойной цикл for""" res = "" # Цикл по i = 1, 2, ..., n-1, n for i in range(1, n + 1): # Цикл по j = 1, 2, ..., n-1, n for j in range(1, n + 1): res += f"({i}, {j}), " return res ``` === "C++" ```cpp title="iteration.cpp" /* Двойной цикл for */ string nestedForLoop(int n) { ostringstream res; // Цикл по i = 1, 2, ..., n-1, n for (int i = 1; i <= n; ++i) { // Цикл по j = 1, 2, ..., n-1, n for (int j = 1; j <= n; ++j) { res << "(" << i << ", " << j << "), "; } } return res.str(); } ``` === "Java" ```java title="iteration.java" /* Двойной цикл for */ String nestedForLoop(int n) { StringBuilder res = new StringBuilder(); // Цикл по i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // Цикл по j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { res.append("(" + i + ", " + j + "), "); } } return res.toString(); } ``` === "C#" ```csharp title="iteration.cs" /* Двойной цикл for */ string NestedForLoop(int n) { StringBuilder res = new(); // Цикл по i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // Цикл по j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { res.Append($"({i}, {j}), "); } } return res.ToString(); } ``` === "Go" ```go title="iteration.go" /* Двойной цикл for */ func nestedForLoop(n int) string { res := "" // Цикл по i = 1, 2, ..., n-1, n for i := 1; i <= n; i++ { for j := 1; j <= n; j++ { // Цикл по j = 1, 2, ..., n-1, n res += fmt.Sprintf("(%d, %d), ", i, j) } } return res } ``` === "Swift" ```swift title="iteration.swift" /* Двойной цикл for */ func nestedForLoop(n: Int) -> String { var res = "" // Цикл по i = 1, 2, ..., n-1, n for i in 1 ... n { // Цикл по j = 1, 2, ..., n-1, n for j in 1 ... n { res.append("(\(i), \(j)), ") } } return res } ``` === "JS" ```javascript title="iteration.js" /* Двойной цикл for */ function nestedForLoop(n) { let res = ''; // Цикл по i = 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { // Цикл по j = 1, 2, ..., n-1, n for (let j = 1; j <= n; j++) { res += `(${i}, ${j}), `; } } return res; } ``` === "TS" ```typescript title="iteration.ts" /* Двойной цикл for */ function nestedForLoop(n: number): string { let res = ''; // Цикл по i = 1, 2, ..., n-1, n for (let i = 1; i <= n; i++) { // Цикл по j = 1, 2, ..., n-1, n for (let j = 1; j <= n; j++) { res += `(${i}, ${j}), `; } } return res; } ``` === "Dart" ```dart title="iteration.dart" /* Двойной цикл for */ String nestedForLoop(int n) { String res = ""; // Цикл по i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // Цикл по j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { res += "($i, $j), "; } } return res; } ``` === "Rust" ```rust title="iteration.rs" /* Двойной цикл for */ fn nested_for_loop(n: i32) -> String { let mut res = vec![]; // Цикл по i = 1, 2, ..., n-1, n for i in 1..=n { // Цикл по j = 1, 2, ..., n-1, n for j in 1..=n { res.push(format!("({}, {}), ", i, j)); } } res.join("") } ``` === "C" ```c title="iteration.c" /* Двойной цикл for */ char *nestedForLoop(int n) { // n * n — это число соответствующих точек, а максимальная длина строки "(i, j), " равна 6+10*2, плюс дополнительное место для завершающего нулевого символа \0 int size = n * n * 26 + 1; char *res = malloc(size * sizeof(char)); // Цикл по i = 1, 2, ..., n-1, n for (int i = 1; i <= n; i++) { // Цикл по j = 1, 2, ..., n-1, n for (int j = 1; j <= n; j++) { char tmp[26]; snprintf(tmp, sizeof(tmp), "(%d, %d), ", i, j); strncat(res, tmp, size - strlen(res) - 1); } } return res; } ``` === "Kotlin" ```kotlin title="iteration.kt" /* Двойной цикл for */ fun nestedForLoop(n: Int): String { val res = StringBuilder() // Цикл по i = 1, 2, ..., n-1, n for (i in 1..n) { // Цикл по j = 1, 2, ..., n-1, n for (j in 1..n) { res.append(" ($i, $j), ") } } return res.toString() } ``` === "Ruby" ```ruby title="iteration.rb" ### Двойной цикл for ### def nested_for_loop(n) res = "" # Цикл по i = 1, 2, ..., n-1, n for i in 1..n # Цикл по j = 1, 2, ..., n-1, n for j in 1..n res += "(#{i}, #{j}), " end end res end ``` ??? pythontutor "Визуализация кода" На рисунке 2-2 приведена блок-схема такого вложенного цикла. { class="animation-figure" }Рисунок 2-2 Блок-схема вложенного цикла
В этом случае количество выполненных действий пропорционально $n^2$ , или, другими словами, время выполнения алгоритма и размер входных данных $n$ находятся в квадратичной зависимости. Можно и дальше добавлять вложенные циклы, тогда каждое вложение будет повышать размерность, увеличивая временную сложность до кубической зависимости, зависимости четвертой степени и так далее. ## 2.2.2 Рекурсия Рекурсия (recursion) - это стратегия алгоритма, при которой функция вызывает саму себя для решения задачи. Она включает два основных этапа. 1. **Вызов**: программа постоянно вызывает саму себя, обычно передавая меньшие или более упрощенные параметры, пока не будет достигнуто условие завершения. 2. **Возврат**: после срабатывания условия завершения программа начинает возвращаться из самой глубокой рекурсивной функции, объединяя результаты каждого уровня. С точки зрения реализации рекурсивный код включает три основных элемента. 1. **Условие завершения**: используется для определения момента перехода от вызова к возврату. 2. **Рекурсивный вызов**: соответствует вызову, функция вызывает саму себя, обычно с меньшими или упрощенными параметрами. 3. **Возврат результата**: соответствует возврату, возвращает результат текущего уровня рекурсии на предыдущий уровень. Рассмотрим следующий код: вызов функции `recur(n)` позволяет вычислить сумму $1 + 2 + \dots + n$ : === "Python" ```python title="recursion.py" def recur(n: int) -> int: """Рекурсия""" # Условие завершения if n == 1: return 1 # Рекурсия: рекурсивный вызов res = recur(n - 1) # Возврат: вернуть результат return n + res ``` === "C++" ```cpp title="recursion.cpp" /* Рекурсия */ int recur(int n) { // Условие завершения if (n == 1) return 1; // Рекурсия: рекурсивный вызов int res = recur(n - 1); // Возврат: вернуть результат return n + res; } ``` === "Java" ```java title="recursion.java" /* Рекурсия */ int recur(int n) { // Условие завершения if (n == 1) return 1; // Рекурсия: рекурсивный вызов int res = recur(n - 1); // Возврат: вернуть результат return n + res; } ``` === "C#" ```csharp title="recursion.cs" /* Рекурсия */ int Recur(int n) { // Условие завершения if (n == 1) return 1; // Рекурсия: рекурсивный вызов int res = Recur(n - 1); // Возврат: вернуть результат return n + res; } ``` === "Go" ```go title="recursion.go" /* Рекурсия */ func recur(n int) int { // Условие завершения if n == 1 { return 1 } // Рекурсия: рекурсивный вызов res := recur(n - 1) // Возврат: вернуть результат return n + res } ``` === "Swift" ```swift title="recursion.swift" /* Рекурсия */ func recur(n: Int) -> Int { // Условие завершения if n == 1 { return 1 } // Рекурсия: рекурсивный вызов let res = recur(n: n - 1) // Возврат: вернуть результат return n + res } ``` === "JS" ```javascript title="recursion.js" /* Рекурсия */ function recur(n) { // Условие завершения if (n === 1) return 1; // Рекурсия: рекурсивный вызов const res = recur(n - 1); // Возврат: вернуть результат return n + res; } ``` === "TS" ```typescript title="recursion.ts" /* Рекурсия */ function recur(n: number): number { // Условие завершения if (n === 1) return 1; // Рекурсия: рекурсивный вызов const res = recur(n - 1); // Возврат: вернуть результат return n + res; } ``` === "Dart" ```dart title="recursion.dart" /* Рекурсия */ int recur(int n) { // Условие завершения if (n == 1) return 1; // Рекурсия: рекурсивный вызов int res = recur(n - 1); // Возврат: вернуть результат return n + res; } ``` === "Rust" ```rust title="recursion.rs" /* Рекурсия */ fn recur(n: i32) -> i32 { // Условие завершения if n == 1 { return 1; } // Рекурсия: рекурсивный вызов let res = recur(n - 1); // Возврат: вернуть результат n + res } ``` === "C" ```c title="recursion.c" /* Рекурсия */ int recur(int n) { // Условие завершения if (n == 1) return 1; // Рекурсия: рекурсивный вызов int res = recur(n - 1); // Возврат: вернуть результат return n + res; } ``` === "Kotlin" ```kotlin title="recursion.kt" /* Рекурсия */ fun recur(n: Int): Int { // Условие завершения if (n == 1) return 1 // Рекурсивный шаг: рекурсивный вызов val res = recur(n - 1) // Возврат: вернуть результат return n + res } ``` === "Ruby" ```ruby title="recursion.rb" ### Рекурсия ### def recur(n) # Условие завершения return 1 if n == 1 # Рекурсия: рекурсивный вызов res = recur(n - 1) # Возврат: вернуть результат n + res end ``` ??? pythontutor "Визуализация кода" На рисунке 2-3 представлен рекурсивный процесс этой функции. { class="animation-figure" }Рисунок 2-3 Рекурсивный процесс функции суммирования
Хотя с точки зрения вычислений итерация и рекурсия могут давать одинаковый результат, **они представляют собой совершенно разные парадигмы мышления и решения задач**. - **Итерация**: решение задачи снизу вверх. Начинаем с самых базовых шагов, которые затем повторяются или накапливаются до завершения задачи. - **Рекурсия**: решение задачи сверху вниз. Исходная задача разбивается на более мелкие подзадачи, которые имеют ту же форму, что и исходная задача. Далее подзадачи продолжают делиться на еще более мелкие, пока не достигается базовый случай, решение которого известно. Рассмотрим в качестве примера вышеупомянутую функцию суммирования, где решается задача $f(n) = 1 + 2 + \dots + n$ . - **Итерация**: моделирование процесса суммирования в цикле проходит от $1$ до $n$ , выполняя операцию суммирования на каждом шаге, чтобы получить итоговое значение $f(n)$ . - **Рекурсия**: последовательное разбиение задачи на подзадачи вида $f(n) = n + f(n - 1)$ до достижения базового случая $f(1) = 1$ . ### 1. Стек вызовов Каждый раз, когда рекурсивная функция вызывает саму себя, система выделяет память для нового вызова функции, чтобы хранить локальные переменные, адрес вызова и другую информацию. Это поведение имеет два последствия. - Контекстные данные функции хранятся в области памяти, называемой пространством стекового кадра, и освобождаются только после возврата функции. **Поэтому рекурсия обычно требует больше памяти, чем итерация**. - Рекурсивный вызов функции создает дополнительные накладные расходы. **Поэтому рекурсия обычно менее эффективна по времени, чем цикл**. Как показано на рисунке 2-4, до срабатывания условия завершения одновременно существует $n$ невозвращенных рекурсивных функций, а **глубина рекурсии равна $n$** . { class="animation-figure" }Рисунок 2-4 Глубина рекурсивного вызова
На практике глубина рекурсии, разрешенная языком программирования, обычно ограничена, и слишком глубокая рекурсия может привести к ошибке переполнения стека. ### 2. Хвостовая рекурсия Интересно, что **если рекурсивный вызов происходит на последнем шаге перед возвратом функции** , то компилятор или интерпретатор может оптимизировать этот вызов, сделав его по эффективности использования памяти сопоставимым с итерацией. Это называется хвостовой рекурсией (tail recursion). - **Обычная рекурсия**: когда функция возвращается на предыдущий уровень, необходимо продолжить выполнение кода, поэтому системе нужно сохранить контекст предыдущего вызова. - **Хвостовая рекурсия**: рекурсивный вызов является последней операцией перед возвратом функции, что означает, что после возврата на предыдущий уровень не требуется выполнять другие операции, поэтому системе не нужно сохранять контекст предыдущей функции. В качестве примера вычисления суммы $1 + 2 + \dots + n$ можно установить переменную результата `res` в качестве параметра функции, чтобы реализовать хвостовую рекурсию: === "Python" ```python title="recursion.py" def tail_recur(n, res): """Хвостовая рекурсия""" # Условие завершения if n == 0: return res # Хвостовой рекурсивный вызов return tail_recur(n - 1, res + n) ``` === "C++" ```cpp title="recursion.cpp" /* Хвостовая рекурсия */ int tailRecur(int n, int res) { // Условие завершения if (n == 0) return res; // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n); } ``` === "Java" ```java title="recursion.java" /* Хвостовая рекурсия */ int tailRecur(int n, int res) { // Условие завершения if (n == 0) return res; // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n); } ``` === "C#" ```csharp title="recursion.cs" /* Хвостовая рекурсия */ int TailRecur(int n, int res) { // Условие завершения if (n == 0) return res; // Хвостовой рекурсивный вызов return TailRecur(n - 1, res + n); } ``` === "Go" ```go title="recursion.go" /* Хвостовая рекурсия */ func tailRecur(n int, res int) int { // Условие завершения if n == 0 { return res } // Хвостовой рекурсивный вызов return tailRecur(n-1, res+n) } ``` === "Swift" ```swift title="recursion.swift" /* Хвостовая рекурсия */ func tailRecur(n: Int, res: Int) -> Int { // Условие завершения if n == 0 { return res } // Хвостовой рекурсивный вызов return tailRecur(n: n - 1, res: res + n) } ``` === "JS" ```javascript title="recursion.js" /* Хвостовая рекурсия */ function tailRecur(n, res) { // Условие завершения if (n === 0) return res; // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n); } ``` === "TS" ```typescript title="recursion.ts" /* Хвостовая рекурсия */ function tailRecur(n: number, res: number): number { // Условие завершения if (n === 0) return res; // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n); } ``` === "Dart" ```dart title="recursion.dart" /* Хвостовая рекурсия */ int tailRecur(int n, int res) { // Условие завершения if (n == 0) return res; // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n); } ``` === "Rust" ```rust title="recursion.rs" /* Хвостовая рекурсия */ fn tail_recur(n: i32, res: i32) -> i32 { // Условие завершения if n == 0 { return res; } // Хвостовой рекурсивный вызов tail_recur(n - 1, res + n) } ``` === "C" ```c title="recursion.c" /* Хвостовая рекурсия */ int tailRecur(int n, int res) { // Условие завершения if (n == 0) return res; // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n); } ``` === "Kotlin" ```kotlin title="recursion.kt" /* Хвостовая рекурсия */ tailrec fun tailRecur(n: Int, res: Int): Int { // Добавить ключевое слово tailrec, чтобы включить оптимизацию хвостовой рекурсии // Условие завершения if (n == 0) return res // Хвостовой рекурсивный вызов return tailRecur(n - 1, res + n) } ``` === "Ruby" ```ruby title="recursion.rb" ### Хвостовая рекурсия ### def tail_recur(n, res) # Условие завершения return res if n == 0 # Хвостовой рекурсивный вызов tail_recur(n - 1, res + n) end ``` ??? pythontutor "Визуализация кода" Процесс выполнения хвостовой рекурсии показан на рисунке 2-5. Сравнивая обычную и хвостовую рекурсии, можно заметить, что точка выполнения операции суммирования у них различается. - **Обычная рекурсия**: операция суммирования выполняется в процессе возврата, после каждого возврата необходимо снова выполнить операцию суммирования. - **Хвостовая рекурсия**: операция суммирования выполняется в процессе вызова, а процесс возврата требует только последовательного возврата. { class="animation-figure" }Рисунок 2-5 Процесс хвостовой рекурсии
!!! tip Обратите внимание: многие компиляторы и интерпретаторы не поддерживают оптимизацию хвостовой рекурсии. Например, Python по умолчанию такую оптимизацию не выполняет, поэтому даже функция в хвостово-рекурсивной форме все равно может привести к переполнению стека. ### 3. Дерево рекурсии При решении задач, связанных с алгоритмами типа «разделяй и властвуй», рекурсия часто оказывается более интуитивной и читабельной, чем итерация. Рассмотрим в качестве примера последовательность Фибоначчи. !!! question Дана последовательность Фибоначчи $0, 1, 1, 2, 3, 5, 8, 13, \dots$. Найди $n$-й элемент этой последовательности. Обозначив $n$-й член последовательности Фибоначчи как $f(n)$ , можно сформулировать два утверждения. - Первые два числа последовательности: $f(1) = 0$ и $f(2) = 1$ . - Каждое число последовательности является суммой двух предыдущих чисел, то есть $f(n) = f(n - 1) + f(n - 2)$ . Используя рекурсивные вызовы в соответствии с рекуррентным соотношением и принимая первые два числа за условия остановки, можно написать рекурсивный код. Вызов `fib(n)` позволит получить $n$-й член последовательности Фибоначчи: === "Python" ```python title="recursion.py" def fib(n: int) -> int: """Последовательность Фибоначчи: рекурсия""" # Условие завершения: f(1) = 0, f(2) = 1 if n == 1 or n == 2: return n - 1 # Рекурсивный вызов f(n) = f(n-1) + f(n-2) res = fib(n - 1) + fib(n - 2) # Вернуть результат f(n) return res ``` === "C++" ```cpp title="recursion.cpp" /* Последовательность Фибоначчи: рекурсия */ int fib(int n) { // Условие завершения: f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Рекурсивный вызов f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // Вернуть результат f(n) return res; } ``` === "Java" ```java title="recursion.java" /* Последовательность Фибоначчи: рекурсия */ int fib(int n) { // Условие завершения: f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Рекурсивный вызов f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // Вернуть результат f(n) return res; } ``` === "C#" ```csharp title="recursion.cs" /* Последовательность Фибоначчи: рекурсия */ int Fib(int n) { // Условие завершения: f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Рекурсивный вызов f(n) = f(n-1) + f(n-2) int res = Fib(n - 1) + Fib(n - 2); // Вернуть результат f(n) return res; } ``` === "Go" ```go title="recursion.go" /* Последовательность Фибоначчи: рекурсия */ func fib(n int) int { // Условие завершения: f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1 } // Рекурсивный вызов f(n) = f(n-1) + f(n-2) res := fib(n-1) + fib(n-2) // Вернуть результат f(n) return res } ``` === "Swift" ```swift title="recursion.swift" /* Последовательность Фибоначчи: рекурсия */ func fib(n: Int) -> Int { // Условие завершения: f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1 } // Рекурсивный вызов f(n) = f(n-1) + f(n-2) let res = fib(n: n - 1) + fib(n: n - 2) // Вернуть результат f(n) return res } ``` === "JS" ```javascript title="recursion.js" /* Последовательность Фибоначчи: рекурсия */ function fib(n) { // Условие завершения: f(1) = 0, f(2) = 1 if (n === 1 || n === 2) return n - 1; // Рекурсивный вызов f(n) = f(n-1) + f(n-2) const res = fib(n - 1) + fib(n - 2); // Вернуть результат f(n) return res; } ``` === "TS" ```typescript title="recursion.ts" /* Последовательность Фибоначчи: рекурсия */ function fib(n: number): number { // Условие завершения: f(1) = 0, f(2) = 1 if (n === 1 || n === 2) return n - 1; // Рекурсивный вызов f(n) = f(n-1) + f(n-2) const res = fib(n - 1) + fib(n - 2); // Вернуть результат f(n) return res; } ``` === "Dart" ```dart title="recursion.dart" /* Последовательность Фибоначчи: рекурсия */ int fib(int n) { // Условие завершения: f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Рекурсивный вызов f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // Вернуть результат f(n) return res; } ``` === "Rust" ```rust title="recursion.rs" /* Последовательность Фибоначчи: рекурсия */ fn fib(n: i32) -> i32 { // Условие завершения: f(1) = 0, f(2) = 1 if n == 1 || n == 2 { return n - 1; } // Рекурсивный вызов f(n) = f(n-1) + f(n-2) let res = fib(n - 1) + fib(n - 2); // Вернуть результат res } ``` === "C" ```c title="recursion.c" /* Последовательность Фибоначчи: рекурсия */ int fib(int n) { // Условие завершения: f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1; // Рекурсивный вызов f(n) = f(n-1) + f(n-2) int res = fib(n - 1) + fib(n - 2); // Вернуть результат f(n) return res; } ``` === "Kotlin" ```kotlin title="recursion.kt" /* Последовательность Фибоначчи: рекурсия */ fun fib(n: Int): Int { // Условие завершения: f(1) = 0, f(2) = 1 if (n == 1 || n == 2) return n - 1 // Рекурсивный вызов f(n) = f(n-1) + f(n-2) val res = fib(n - 1) + fib(n - 2) // Вернуть результат f(n) return res } ``` === "Ruby" ```ruby title="recursion.rb" ### Последовательность Фибоначчи: рекурсия ### def fib(n) # Условие завершения: f(1) = 0, f(2) = 1 return n - 1 if n == 1 || n == 2 # Рекурсивный вызов f(n) = f(n-1) + f(n-2) res = fib(n - 1) + fib(n - 2) # Вернуть результат f(n) res end ``` ??? pythontutor "Визуализация кода" Проанализировав приведенный код, можно заметить, что внутри функции осуществляется рекурсивный вызов двух функций, **то есть из одного вызова образуются два ветвления**. Как показано на рисунке 2-6, при последующем выполнении рекурсивных вызовов в итоге образуется дерево рекурсии (recursion tree) глубиной $n$ . { class="animation-figure" }Рисунок 2-6 Дерево рекурсии последовательности Фибоначчи
По своей сути рекурсия отражает парадигму мышления «разбиение задачи на более мелкие подзадачи», что делает стратегию «разделяй и властвуй» крайне важной. - С точки зрения **алгоритмов** многие важные алгоритмические стратегии, такие как поиск, сортировка, возврат, «разделяй и властвуй» и динамическое программирование, прямо или косвенно используют этот подход. - С точки зрения **структур данных** рекурсия естественно подходит для решения задач, связанных со списками, деревьями и графами, поскольку они очень хорошо поддаются анализу с использованием идеи «разделяй и властвуй». ## 2.2.3 Сравнение Подводя итог, можно сказать, что итерация и рекурсия различаются по реализации, производительности и применимости, как показано в таблице 2-1.Таблица 2-1 Сравнение итерации и рекурсии