Рекурсия и рекурсивные алгоритмы
Пример 3. Задача о разбиении целого на части.
Найдите количество разбиений натурального числа на сумму натуральных слагаемых.
Разбиение подразумевает представление натурального числа в виде суммы натуральных слагаемых, при этом суммы должны отличаться набором чисел, а не их последовательностью. В разбиение также может входить одно число.
Например, разбиение числа 6 будет представлено 11 комбинациями:
6 5+1 4+2, 4+1+1 3+3, 3+2+1, 3+1+1+1 2+2+2, 2+2+1+1, 2+1+1+1+1 1+1+1+1+1+1
Рассмотрим решение в общем виде. Пусть зависимость R(n,k) вычисляет количество разбиений числа n на сумму слагаемых, не превосходящих k. Опишем свойства R(n,k).
Если в сумме все слагаемые не превосходят 1, то такое представление единственно, то есть R(n,k)=1.
Если рассматриваемое число равно 1, то при любом натуральном значении второго параметра разбиение также единственно: R(n,k)=1.
Если второй параметр превосходит значение первого , то имеет место равенство R(n,k)=R(n,n), так как для представления натурального числа в сумму не могут входить числа, превосходящие его.
Если в сумму входит слагаемое, равное первому параметру, то такое представление также единственно (содержит только это слагаемое), поэтому имеет место равенство: R(n,n)=R(n,n-1)+1.
Осталось рассмотреть случай (n>k). Разобьем все представления числа n на непересекающиеся разложения: в одни обязательно будет входить слагаемое k, а другие суммы не содержат k. Первая группа сумм, содержащая k, эквивалентна зависимости R(n-k,k), что следует после вычитания числа k из каждой суммы. Вторая группа сумм содержит разбиение числа n на слагаемые, каждое из которых не превосходит k-1, то есть число таких представлений равно R(n,k-1). Так как обе группы сумм не пересекаются, то R(n,k)=R(n-k,k)+R(n,k-1).
Разработаем рекурсивную триаду.
Параметризация: Рассмотрим разбиение натурального числа n на сумму таких слагаемых, которые не превосходят натурального числа k.
База рекурсии: исходя из свойств рассмотренной зависимости, выделяются два базовых случая:
при n=1 R(n,k)=1,
при k=1 R(n,k)=1.
Декомпозиция: общий случай задачи сводится к трем случаям, которые и составляют декомпозиционные отношения.
при n=k R(n,k)=R(n,n-1)+1,
при n<k R(n,k)=R(n,n),
при n>k R(n,k)=R(n-k,k)+R(n,k-1).
#include "stdafx.h" #include <iostream> using namespace std; unsigned long int Razbienie(unsigned long int n, unsigned long int k); int _tmain(int argc, _TCHAR* argv[]){ unsigned long int number, max,num; printf ("\nВведите натуральное число: "); scanf ("%d", &number); printf ("Введите максимальное натуральное слагаемое в сумме: "); scanf ("%d", &max); num=Razbienie(number,max); printf ("Число %d можно представить в виде суммы с максимальным слагаемым %d.", number, max); printf ("\nКоличество разбиений равно %d",num); system("pause"); return 0; } unsigned long int Razbienie(unsigned long int n, unsigned long int k){ if(n==1 || k==1) return 1; if(n<=k) return Razbienie(n,n-1)+1; return Razbienie(n,k-1)+Razbienie(n-k,k); }
Пример 4. Задача о переводе натурального числа в шестнадцатеричную систему счисления.
Дано натуральное число, не выходящее за пределы типа unsigned long. Число представлено в десятичной системе счисления. Переведите его в систему счисления с основанием 16.
Пусть требуется перевести целое число n из десятичной в р -ичную систему счисления (по условию задачи, р = 16), то есть найти такое k, чтобы выполнялось равенство n10=kp.
Параметризация: n – данное натуральное число, р – основание системы счисления.
База рекурсии: на основании правил перевода чисел из десятичной системы в систему счисления с основанием р, деление нацело на основание системы выполняется до тех пор, пока неполное частное не станет равным нулю, то есть: если целая часть частного n и р равна нулю, то k = n. Данное условие можно реализовать иначе, сравнив n и р: целая часть частного равна нулю, если n < р.
Декомпозиция: в общем случае k формируется из цифр целой части частного n и р, представленной в системе счисления с основанием р, и остатка от деления n на p.
#include "stdafx.h" #include <iostream> using namespace std; #define maxline 50 void perevod( unsigned long n, unsigned int p,FILE *pf); int _tmain(int argc, _TCHAR* argv[]){ unsigned long number10; unsigned int osn=16; char number16[maxline]; FILE *f; if ((f=fopen("out.txt", "w"))==NULL) perror("out.txt"); else { printf ("\nВведите число в десятичной системе: "); scanf("%ld", &number10); perevod(number10, osn, f); fclose(f); } if ((f=fopen("out.txt", "r"))==NULL) perror("out.txt"); else { fscanf(f,"%s",number16); printf("\n %ld(10)=%s(16)", number10, number16); fclose(f); } system("pause"); return 0; } void perevod(unsigned long n, unsigned int p, FILE *pf){ char c; unsigned int r; if(n >= p) perevod (n/p, p, pf);//декомпозиция r=n%p; c=r < 10 ? char (r+48) : char (r+55); putc(c, pf); }
Ключевые термины
База рекурсии – это тривиальный случай, при котором решение задачи очевидно, то есть не требуется обращение функции к себе.
Глубина рекурсивных вызовов – это наибольшее одновременное количество рекурсивных обращений функции, определяющее максимальное количество слоев рекурсивного стека.
Декомпозиция – это выражение общего случая через более простые подзадачи с измененными параметрами.
Корень полного дерева рекурсивных вызовов – это вершина полного дерева рекурсии, соответствующая начальному обращению к функции.
Косвенная (взаимная) рекурсия – это последовательность взаимных вызовов нескольких функций, организованная в виде циклического замыкания на тело первоначальной функции, но с иным набором параметров.
Объем рекурсии - это характеристика сложности рекурсивных вычислений для конкретного набора параметров, представляющая собой количество вершин полного рекурсивного дерева без единицы.
Параметризация – это выделение из постановки задачи параметров, которые используются для описания условия задачи и решения.
Полное дерево рекурсии – это граф, вершинами которого являются наборы фактических параметров при всех вызовах функции, начиная с первого обращения к ней, а ребрами – пары таких наборов, соответствующих взаимным вызовам.
Прямая рекурсия – это непосредственное обращение рекурсивной функции к себе, но с иным набором входных данных.
Рекурсивная триада – это этапы решения задач рекурсивным методом.
Рекурсивная функция – это функция, которая в своем теле содержит обращение к самой себе с измененным набором параметров.
Рекурсивный алгоритм – это алгоритм, в определении которого содержится прямой или косвенный вызов этого же алгоритма.
Рекурсия – это определение объекта посредством ссылки на себя.
Краткие итоги
- Рекурсия характеризуется определением объекта посредством ссылки на себя.
- Рекурсивные алгоритмы содержат в своем теле прямое или опосредованное обращение с самим себе.
- Рекурсивные функции содержат в своем теле обращение к самим себе с измененным набором параметров в виде прямой рекурсии. При этом обращение к себе может быть организовано посредством косвенной рекурсии – через цепочку взаимных обращений функций, замыкающихся в итоге на первоначальную функцию.
- Решение задач рекурсивными способами проводится посредством разработки рекурсивной триады.
- Целесообразность применения рекурсии в программировании обусловлена спецификой задач, в постановке которых явно или опосредовано указывается на возможность сведения задачи к подзадачам, аналогичным самой задаче.
- Рекурсивные методы решения задач широко используются при моделировании задач из различных предметных областей.
- Рекурсивные алгоритмы относятся к ресурсоемким алгоритмам. Для оценки сложности рекурсивных алгоритмов учитывается число вершин полного рекурсивного дерева, количество передаваемых параметров, временные затраты на организацию стековых слоев.