Рекурсивные функции в си. Рекурсия. Тренировочные задачи

Здравствуй Хабрахабр!

В этой статье речь пойдет о задачах на рекурсию и о том как их решать.

Кратко о рекурсии

Рекурсия достаточно распространённое явление, которое встречается не только в областях науки, но и в повседневной жизни. Например, эффект Дросте, треугольник Серпинского и т. д. Один из вариантов увидеть рекурсию – это навести Web-камеру на экран монитора компьютера, естественно, предварительно её включив. Таким образом, камера будет записывать изображение экрана компьютера, и выводить его же на этот экран, получится что-то вроде замкнутого цикла. В итоге мы будем наблюдать нечто похожее на тоннель.

В программировании рекурсия тесно связана с функциями, точнее именно благодаря функциям в программировании существует такое понятие как рекурсия или рекурсивная функция. Простыми словами, рекурсия – определение части функции (метода) через саму себя, то есть это функция, которая вызывает саму себя, непосредственно (в своём теле) или косвенно (через другую функцию).

О рекурсии сказано много. Вот несколько хороших ресурсов:

  • Рекурсия и рекурсивные задачи. Области применение рекурсии
Предполагается что читатель теоритически знаком с рекурсией и знает что это такое. В данной статье мы бóльшее вниманиее уделим задачам на рекурсию.

Задачи

При изучении рекурсии наиболее эффективным для понимания рекурсии является решение задач.
Как же решать задачи на рекурсию?
В первую очередь надо понимать что рекурсия это своего рода перебор. Вообще говоря, всё то, что решается итеративно можно решить рекурсивно, то есть с использованием рекурсивной функции.

из сети

Любой алгоритм, реализованный в рекурсивной форме, может быть переписан в итерационном виде и наоборот. Останется вопрос, надо ли это, и насколько это будет это эффективно.

Для обоснования можно привести такие доводы.

Для начала можно вспомнить определение рекурсии и итерации. Рекурсия - это такой способ организации обработки данных, при котором программа вызывает сама себя непосредственно, либо с помощью других программ. Итерация - это способ организации обработки данных, при котором определенные действия повторяются многократно, не приводя при этом к рекурсивным вызовам программ.

После чего можно сделать вывод, что они взаимно заменимы, но не всегда с одинаковыми затратами по ресурсам и скорости. Для обоснования можно привести такой пример: имеется функция, в которой для организации некого алгоритма имеется цикл, выполняющий последовательность действий в зависимости от текущего значения счетчика (может от него и не зависеть). Раз имеется цикл, значит, в теле повторяется последовательность действий - итерации цикла. Можно вынести операции в отдельную подпрограмму и передавать ей значение счетчика, если таковое есть. По завершению выполнения подпрограммы мы проверяем условия выполнения цикла, и если оно верно, переходим к новому вызову подпрограммы, если ложно - завершаем выполнение. Т.к. все содержание цикла мы поместили в подпрограмму, значит, условие на выполнение цикла помещено также в подпрограмму, и получить его можно через возвращающее значение функции, параметры передающееся по ссылке или указателю в подпрограмму, а также глобальные переменные. Далее легко показать, что вызов данной подпрограммы из цикла легко переделать на вызов, или не вызов (возврата значения или просто завершения работы) подпрограммы из нее самой, руководствуясь какими-либо условиями (теми, что раньше были в условии цикла). Теперь, если посмотреть на нашу абстрактную программу, она примерно выглядит как передача значений подпрограмме и их использование, которые изменит подпрограмма по завершению, т.е. мы заменили итеративный цикл на рекурсивный вызов подпрограммы для решения данного алгоритма.

Задача по приведению рекурсии к итеративному подходу симметрична.

Подводя итог, можно выразить такие мысли: для каждого подхода существует свой класс задач, который определяется по конкретным требованиям к конкретной задаче.

Более подробно с этим можно познакомиться


Так же как и у перебора (цикла) у рекурсии должно быть условие остановки - Базовый случай (иначе также как и цикл рекурсия будет работать вечно - infinite). Это условие и является тем случаем к которому рекурсия идет (шаг рекурсии). При каждом шаге вызывается рекурсивная функция до тех пор пока при следующем вызове не сработает базовое условие и произойдет остановка рекурсии(а точнее возврат к последнему вызову функции). Всё решение сводится к решению базового случая. В случае, когда рекурсивная функция вызывается для решения сложной задачи (не базового случая) выполняется некоторое количество рекурсивных вызовов или шагов, с целью сведения задачи к более простой. И так до тех пор пока не получим базовое решение.

Итак рекурсивная функция состоит из

  • Условие остановки или же Базовый случай
  • Условие продолжения или Шаг рекурсии - способ сведения задачи к более простым.
Рассмотрим это на примере нахождения факториала :

Public class Solution { public static int recursion(int n) { // условие выхода // Базовый случай // когда остановиться повторять рекурсию? if (n == 1) { return 1; } // Шаг рекурсии / рекурсивное условие return recursion(n - 1) * n; } public static void main(String args) { System.out.println(recursion(5)); // вызов рекурсивной функции } }

Тут Базовым условием является условие когда n=1. Так как мы знаем что 1!=1 и для вычисления 1! нам ни чего не нужно. Чтобы вычислить 2! мы можем использовать 1!, т.е. 2!=1!*2. Чтобы вычислить 3! нам нужно 2!*3… Чтобы вычислить n! нам нужно (n-1)!*n. Это и является шагом рекурсии. Иными словами, чтобы получить значение факториала от числа n, достаточно умножить на n значение факториала от предыдущего числа.

Теги: Добавить метки

Чтобы не писать одну огромную статью, где будут многочисленные примеры рекурсии в C++ Напишу еще 1 пример рекурсии тут. По большому счету, кто понял основу и использование прямой рекурсии в своих функциях, могут пропустить этот материал. Здесь пример использования рекурсии, как в статье Функции в С++ для начинающих. Рекурсия

Задача 1 – С помощью рекурсии Вывести на экран факториал числа от 1 до N
Пишем код
=============================
ЭТАП №1 пишем пустую программу
=============================

#include

#include

#include

int main ()
{
system (“cls” );

getch ();
return 0 ;
}

Пустая программа создана, комментировать думаю не нужно
ЭТАП №2 пишем пишем саму рекурсивную функцию
=========================================

#include

#include

#include

//Наша рекурсивная функция
int fact (int N )
{

//0! = 1, 1!=1, 2!=2, 3!=6 … Т.к. первые 2 числа единицы и идут не в строгой последовательности, мы принудительно прописываем этот момент в коде

if n <2 return 1 ;
else return n *fact (n –1 ) //тут функция вызывает саму себя

}

int main ()
{
system (“cls” );
cout < //Точка вызова рекурсивной функции. Вывод факториала 10 на экран
getch ();
return 0 ;
}
ddd
============

Главный кусочек в программе рекурсии С++
return n *fact (n –1 )

Наша функция ведет перерасчет для получения предшествующего значения. Настоящим значением является параметр, переданный в n из точки вызова функции. Точка вызова нашей функции – это вызов её из основного блока программы. В нашем случае вызываем мы ее из функции int main ()
Почему я пишу не следующее, а предшествующее. Когда числа умножаются, то сначала 0 *1 тут наше настоящее значение 1 , а ноль предыдущее значение расчета. В этом и есть вся суть рекурсии, настоящее значение мы вычисляем с помощью предшествующего, при этом предшествующее значение получено этим же вычислением. Компилятор сам вычисляет предшествующее значение и сам занимается хранением этого значения в памяти. От нас нужно только дать инструкции . Благодаря такой возможности компиляторов, функция встретив инструкцию на вызов самой себя (у нас fact (n –1 ) )не затирает параметр переданный в n для расчета функции. Параметр, переданный в n так и остается в памяти. При этом дополнительно определяется другая область памяти, в которой наша рекурсивная функция и выполняет рекурсивное вычисления для получения предшествующего результата.

Да простят меня программисты за столь разжеванное суждение. Приблизительно так получается воспринимают рекурсию новички.

Надеюсь этот материал блога С++ для начинающих стал кому-то полезным и помог понять базовое понятие рекурсивной функции в C++

Примечание. В этой статье как и в прошлой, я сделал вычисление не 1 до N, а до значения, введенного внутри программы. Смысл в том, что я не хотел писать лишнюю строчку кода и предполагал, что пользователь уже хорошо ориентируется в вводе данных и выводе их на экран.

Varg говорит:

Добрый день, упорото ни могу понять как производит вычисления данная функция

{
if (n==1) return 1; //если новое значение 1, то будем складывать его с 1, а не с предыдущим. т.к. предыдущее - это ноль. и сложение 1+0 будет бесконечным.
else return sum(n-1)+n; //но если n>1, то складываем его с предыдущим значением, равным сумме всех элементов до n
}

По моему разумению в n находиться 5 условие не совпадает тогда выполняется данный код sum(n-1)+n то есть к 5 прибавляется что то полученное в скобках путем вычитания но что (5 - 1)+5 и если так то что останавливает данное арифметическое действие:?: :?: :?: Что за предыдущее значение, откуда оно берется чему равно:?: :?: :?:

Да почти все так как я и понял (в последнем абзаце вы показали рекурсию))), но остаться вопрос как получается сумма, которая потом вы водиться на экран?
Я работаю с Dev C++ у меня данный пример выводит сумму ==15, если по считать как написано в примере то сумма получается другой.
Я писал выше вот возьмем (5-1)+5=4+5=9

:
1+2+3+4+5 = 15. Пример правильно выводит.

(5) //В функцию дали 5, проверили ее на равенство с единицей. не равно, вызвали функцию снова, отдав в нее 5-1
(5-1+(5)) //...
(4-1+(5-1+(5)))
(3-1+(4-1+(5-1+(5))))
(2-1+(3-1+(4-1+(5-1+(5)))))

2-1 == 1, закончили вызывать функцию.
(2-1+(3-1+(4-1+(5-1+(5))))) == 15
это и есть результат.
Здесь результат работы функции - это разность первых двух чисел, а n - это вся остальная часть справа
__________________________________
Просто нужно функцию правильно понимать и принимать как вычисляемое значение и не понимать ее как переменную. Она похожа на переменную, но ближе к вычисляемой константе., хотя константой и не является, просто воспринимать так удобнее.

Да да да не успел написать, что понял, все верно, что то сразу не дошло. Спасибо вам хороший сайт))

И еще не совсем логично получается в 8й строке если поменять возвращаемое число с return 1 на 2 сумма меняется на 16 как это условие связано с 9й строкой?
С этим тоже все понятно просто return 2 добавляет к сумме так сказать свою лишению денницу.

:
не лишнюю денницу, а эту двойку, а если написать -3, то при сложении один раз будет отнимать тройку и т.д.
Вся логика в том, что любой рекурсивной функции нужна точка возврата.
Связь с девятой строкой в том, что в функцию sum при вызове её из внутри main передается число, при рекурсивных вызовах это число каждый раз уменьшается на единицу (n-1), этот результат n-1 проверяется на равенство с единицей и если равенство истинно, то вся полученная сумма будет просуммирована с тем числом, который в том return. иначе вся общая сумма будет просуммирована с этим новым n-1

Здравствуй Хабрахабр!

В этой статье речь пойдет о задачах на рекурсию и о том как их решать.

Кратко о рекурсии

Рекурсия достаточно распространённое явление, которое встречается не только в областях науки, но и в повседневной жизни. Например, эффект Дросте, треугольник Серпинского и т. д. Один из вариантов увидеть рекурсию – это навести Web-камеру на экран монитора компьютера, естественно, предварительно её включив. Таким образом, камера будет записывать изображение экрана компьютера, и выводить его же на этот экран, получится что-то вроде замкнутого цикла. В итоге мы будем наблюдать нечто похожее на тоннель.

В программировании рекурсия тесно связана с функциями, точнее именно благодаря функциям в программировании существует такое понятие как рекурсия или рекурсивная функция. Простыми словами, рекурсия – определение части функции (метода) через саму себя, то есть это функция, которая вызывает саму себя, непосредственно (в своём теле) или косвенно (через другую функцию).

О рекурсии сказано много. Вот несколько хороших ресурсов:

  • Рекурсия и рекурсивные задачи. Области применение рекурсии
Предполагается что читатель теоритически знаком с рекурсией и знает что это такое. В данной статье мы бóльшее вниманиее уделим задачам на рекурсию.

Задачи

При изучении рекурсии наиболее эффективным для понимания рекурсии является решение задач.
Как же решать задачи на рекурсию?
В первую очередь надо понимать что рекурсия это своего рода перебор. Вообще говоря, всё то, что решается итеративно можно решить рекурсивно, то есть с использованием рекурсивной функции.

из сети

Любой алгоритм, реализованный в рекурсивной форме, может быть переписан в итерационном виде и наоборот. Останется вопрос, надо ли это, и насколько это будет это эффективно.

Для обоснования можно привести такие доводы.

Для начала можно вспомнить определение рекурсии и итерации. Рекурсия - это такой способ организации обработки данных, при котором программа вызывает сама себя непосредственно, либо с помощью других программ. Итерация - это способ организации обработки данных, при котором определенные действия повторяются многократно, не приводя при этом к рекурсивным вызовам программ.

После чего можно сделать вывод, что они взаимно заменимы, но не всегда с одинаковыми затратами по ресурсам и скорости. Для обоснования можно привести такой пример: имеется функция, в которой для организации некого алгоритма имеется цикл, выполняющий последовательность действий в зависимости от текущего значения счетчика (может от него и не зависеть). Раз имеется цикл, значит, в теле повторяется последовательность действий - итерации цикла. Можно вынести операции в отдельную подпрограмму и передавать ей значение счетчика, если таковое есть. По завершению выполнения подпрограммы мы проверяем условия выполнения цикла, и если оно верно, переходим к новому вызову подпрограммы, если ложно - завершаем выполнение. Т.к. все содержание цикла мы поместили в подпрограмму, значит, условие на выполнение цикла помещено также в подпрограмму, и получить его можно через возвращающее значение функции, параметры передающееся по ссылке или указателю в подпрограмму, а также глобальные переменные. Далее легко показать, что вызов данной подпрограммы из цикла легко переделать на вызов, или не вызов (возврата значения или просто завершения работы) подпрограммы из нее самой, руководствуясь какими-либо условиями (теми, что раньше были в условии цикла). Теперь, если посмотреть на нашу абстрактную программу, она примерно выглядит как передача значений подпрограмме и их использование, которые изменит подпрограмма по завершению, т.е. мы заменили итеративный цикл на рекурсивный вызов подпрограммы для решения данного алгоритма.

Задача по приведению рекурсии к итеративному подходу симметрична.

Подводя итог, можно выразить такие мысли: для каждого подхода существует свой класс задач, который определяется по конкретным требованиям к конкретной задаче.

Более подробно с этим можно познакомиться


Так же как и у перебора (цикла) у рекурсии должно быть условие остановки - Базовый случай (иначе также как и цикл рекурсия будет работать вечно - infinite). Это условие и является тем случаем к которому рекурсия идет (шаг рекурсии). При каждом шаге вызывается рекурсивная функция до тех пор пока при следующем вызове не сработает базовое условие и произойдет остановка рекурсии(а точнее возврат к последнему вызову функции). Всё решение сводится к решению базового случая. В случае, когда рекурсивная функция вызывается для решения сложной задачи (не базового случая) выполняется некоторое количество рекурсивных вызовов или шагов, с целью сведения задачи к более простой. И так до тех пор пока не получим базовое решение.

Итак рекурсивная функция состоит из

  • Условие остановки или же Базовый случай
  • Условие продолжения или Шаг рекурсии - способ сведения задачи к более простым.
Рассмотрим это на примере нахождения факториала :

Public class Solution { public static int recursion(int n) { // условие выхода // Базовый случай // когда остановиться повторять рекурсию? if (n == 1) { return 1; } // Шаг рекурсии / рекурсивное условие return recursion(n - 1) * n; } public static void main(String args) { System.out.println(recursion(5)); // вызов рекурсивной функции } }

Тут Базовым условием является условие когда n=1. Так как мы знаем что 1!=1 и для вычисления 1! нам ни чего не нужно. Чтобы вычислить 2! мы можем использовать 1!, т.е. 2!=1!*2. Чтобы вычислить 3! нам нужно 2!*3… Чтобы вычислить n! нам нужно (n-1)!*n. Это и является шагом рекурсии. Иными словами, чтобы получить значение факториала от числа n, достаточно умножить на n значение факториала от предыдущего числа.

Теги:

  • рекурсия
  • задачи
  • java
Добавить метки

Понравилась статья? Поделиться с друзьями: