Рекурсивные методы решения задач динамического программирования
Автор: Шершаков Д.В., Булатникова И.Н.
Журнал: Форум молодых ученых @forum-nauka
Статья в выпуске: 4 (32), 2019 года.
Бесплатный доступ
Произведен анализ рекурсивных методов решения задач. Рассмотрены практические методы решения задач динамического программирования.
Рекурсия, кеширование, мемоизация, динамическое программирование. recursion
Короткий адрес: https://sciup.org/140286151
IDR: 140286151
Текст научной статьи Рекурсивные методы решения задач динамического программирования
Recursion, caching, memoization, dynamic programming.
Динамическое программирование — способ решения сложных задач путём разбиения их на простые подзадачи. Применим данный метод к задачам с оптимальной подструктурой, которые выглядят, как набор перекрывающихся подзадач, с меньшей исходной сложностью. Ключевая идея в динамическом программировании относительно проста. Чтобы решить поставленную задачу, требуется решить отдельные части задачи, после чего объединить решения подзадач в одно общее решение. Часто многие из этих подзадач одинаковы. Подход динамического программирования заключается в том, чтобы решить каждую подзадачу только один раз, сократив тем самым количество вычислений [2]. Это особенно необходимо в случаях, когда число повторяющихся подзадач экспоненциально велико.
Решение задачи динамическим программированием должно содержать следующее:
-
1) Зависимость элементов динамики друг от друга . Такая зависимость может быть прямо дана в условии (как правило возникает при решении задач на числовые последовательности). В противном случае можно попытаться узнать какой-то известный числовой ряд (например, чисел Фибоначчи), вычислив первые несколько значений вручную.
-
2) Значение начальных состояний . В результате долгого разбиения на подзадачи вам необходимо свести функцию либо к уже известным значениям (как в случае с Фибоначчи — заранее определены первые два члена), либо к задаче, решаемой элементарно.
Перед тем, как перейти к непосредственному решению задач, рассмотрим понятие рекурсии. В математике рекурсия имеет отношение к методу определения функций и числовых рядов: рекурсивно заданная функция определяет своё значение через обращение к себе самой с другими аргументами. В программировании рекурсия — вызов функции из неё же самой, непосредственно или через другие функции [1].
В качестве примера рассмотрим задачу, решение которой будет рекурсивным.
Вычислить n-й член последовательности, заданной формулами: a2n an+an-1, a2n+1 an an-1, ag = a1 = 1.
Здесь нам даны и начальные состояния (a 0 = a 1 = 1), и зависимости. Необходимо понимать, что 2n — условие чётности числа, а 2n+1 — нечётности. То есть, нет необходимости проверять, чётно ли число, и считать его в зависимости от этого по разным формулам. Решение данной задачи было написано на языке Java.
private static int f(int n){ if n==0 || n==l) return 1; // Проверка на начальное значение if(n%2==0){ //Проверка на чётность return f(n/2)+f(n/2-l); И Вычисляем по формуле для чётных индексов,
М ссылаясь на предыдущие значения }else{ return f((n-l)/2)-f((n-l)/2-l); H Вычисляем по формуле для нечётных //индексов, ссылаясь на предыдущие значения } }
Рисунок 1 – пример рекурсивного метода решения.
Данное решение хорошо работает, но есть особенности. Если необходимо вычислить f(12), то метод будет вычислять сумму f(6)+f(5). В то же время, f(6)=f(3)+f(2) и f(5)=f(2)-f(1), то есть значение f(2) будет вычисляться дважды. Решить данную проблему поможет — мемоизация или кеширование значений.
Идея мемоизации заключается в следующем. Единожды вычисляя значение, оно заносится в определенную структуру данных [3]. Перед каждым вычислением необходимо проверить, есть ли вычисляемое значение в этой структуре и, если оно существует, использовать его. В качестве структуры данных можно использовать массив, заполненный флаговыми значениями. Если значение элемента по индексу N равно значению флага, значит, его ещё не вычисляли. Это создаёт определённые трудности, так как значение флага не должно принадлежать множеству значений функции, которое не всегда очевидно [4]. Для этого отлично подойдёт использование хэш-таблицы — все действия в ней выполняются за O(1), что сильно поможет в решении задачи. Однако, при большом количестве значений два числа могут иметь одинаковый хэш, что, может порождать проблемы. В таком случае стоит использовать, например, красно-чёрное дерево поиска.
Для уже написанной функции f(int) кэширование значений будет выглядеть следующим образом:
private static
HashMap
}
Рисунок 2 – пример рекурсивного метода решения с кешированием значений.
Современное программирование всё больше помогает в решении сложных математических задач. Прогресс не стоит на месте, вычислительная мощность растёт и уже сегодня можно решать то, что несколько десятков лет назад казалось невозможным. Нестандартные алгоритмы и методы, позволяют находить новые подходы к решению задач. Именно такие рекурсивные методы динамического программирования позволяют решать сложные математические задачи с помощью современных языков высокого уровня, таких как Java.
Список литературы Рекурсивные методы решения задач динамического программирования
- Ульянов М. В., Головешкин В. А. Теория рекурсии для программистов - Физматлит, 2006 г.
- Окулов С. М., Пестов О. А. Динамическое программирование. - Бином. Лаборатория знаний, 2019 г.
- Шилдт Г. Java. Полное руководство - Вильямс, 2018 г.
- Данович Л.М., Наумова Н.А., Булатникова И.Н. И др. Методы математического моделирования технических и технологических процессов / учебное пособие - Краснодар: Кубанский гос.техн.ун - т, 2018-2360.