Опубликован: 02.02.2011 | Доступ: свободный | Студентов: 3338 / 949 | Оценка: 4.43 / 3.57 | Длительность: 33:06:00
Специальности: Программист
Лекция 36:

Решение задач на использование рекурсивных алгоритмов

< Лекция 35 || Лекция 36: 123 || Лекция 37 >

Опорная схема "Характеристические свойства"

Совокупность всех или части условий любой задачи, оформленная в виде некоторого предиката над наборами входных данных и возможных результатов, назовем характеристическим свойством задачи. Если в предикате задействованы все условия задачи, то характеристическое свойство и соответствующий предикат назовем полным, если нет, - частичным. Формальная запись полного или частичного предиката, с одной стороны, позволяет проводить независимую проверку правильности работы ранее разработанных алгоритмов решения данной задачи, а с другой стороны, может оказать существенную помощь для отыскания новых рекурсивных алгоритмов ее решения. Остановимся на примерах, иллюстрирующих второй вариант использования характеристических свойств задачи.

Рассмотрим задачу о "Допустимых последовательностях". Последовательность Q(N) длины N, составленная из символов 0 и 1, называется допустимой, если в ней нет двух подряд идущих символов 1. В противном случае Q(N) называется недопустимой. Определим K(N) - общее количество допустимых последовательностей для натурального значения N.

Методом полного перебора эту задачу можно решить лишь при небольших значениях N, так как количество всевозможных последовательностей равно 2N.

Пусть набор представлен в виде вектора v с компонентами 0 и 1 (с нумерацией их от 0 до N-1 ). Определим предикат P(v), истинный только на допустимых наборах v. Формализованная запись этого предиката выглядит следующим образом:

P(v)=(\forall (0\le i\le N-2))(\neg ((v_{i}=1)(v_{i+1}=1)))=
\\
=(\forall (0\le i\le N-2))((v_{i}=1)\wedge (v_{i+1}=0)\vee (v_{i}=0)\wedge (v_{i+1}=0)\vee (v_{i}=0)\wedge (v_{i+1}=1))=
\\
=(\forall (0\le i\le N-2))((v_{i+1}=0)\vee ((v_{i+1}=1)\wedge (v_{i}=0)))=
\\
=((\forall (0\le i\le N-3))((v_{i+1}=0)\vee ((v_{i+1}=1)\wedge (v_{i}=0))))\wedge 
\\
   \wedge ((v_{N-1}=0)\vee ((v_{N-1}=1)\wedge (v_{N-2}=0)))=
\\
=(((\forall (0\le i\le N-3))((v_{i+1}=0)\vee ((v_{i+1}=1)\wedge (v_{i}=0))))\wedge (v_{N-1}=0))\vee 
\\
\vee (((\forall (0\le i\le N-3))((v_{i+1}=0)\vee ((v_{i+1}=1)\wedge (v_{i}=0))))\wedge ((v_{N-2}=0)\wedge (v_{N-1}=1)))

Фактически формальными преобразованиями предиката мы получили его декомпозицию, то есть множество M(N) допустимых векторов длины N(N >= 3) можно представить в виде объединения двух непересекающихся подмножеств:

M(N)=(M(N-1)x{0})\cup\left(M(N-2)\times \left\{
\begin{pmatrix} 0 \\ 1 \end{pmatrix}
\right\}\right).

Здесь под декартовыми произведениями M(N-1)x{0} и M(N-2)x{(0,1)T} понимаются множества векторов длины N. В первом случае последняя компонента векторов равна 0, а первые N-1 компонентов составляют допустимые векторы длиной N-1. Во втором случае последние две компоненты равны соответственно 0 и 1, а первые N-2 компоненты составляют допустимые векторы длины N-2. Кроме того: M(1)={(0),(1)} ; M(2)={(0,0)T,(0,1)T,(1,0)T}. Отсюда вытекает справедливость следующего рекуррентного соотношения:

K(1)=2, K(2)=3,, K(N)=K(N-1)+K(N-2) (N>=3).

Таким образом, K(N)=F(N+2), то есть искомая последовательность K(N) есть сдвиг последовательности Фибоначчи F(N) на два элемента влево. Для вычисления K(N) можно написать рекурсивную функцию, аналогичную функции вычисления членов последовательности Фибоначчи.

Опорная схема "Перенести часть условий в проверку"

Во многих задачах, сводящихся к рекурсивным алгоритмам, рекурсия в явном виде сразу не обнаруживается или достаточно сложна для алгоритмической реализации. Однако удаление части условий из задачи приводит к новой вспомогательной задаче, рекурсивный алгоритм решения которой строится достаточно несложно. В этом случае чтобы узнать, является ли полученный для новой задачи ответ (ответы) решением исходной задачи, необходимо проверить, выполняются ли для него ранее удаленные условия или нет.

Если решение задачи сводится к вычислению значения истинности некоторого предиката, непосредственно построенного из конъюнкции условий задачи на наборах входных данных, то описанная схема допускает возможность проверки выполнимости удаляемых условий как до использования рекурсивного алгоритма решения вспомогательной задачи, так и после этого. При этом рассматриваемая опорная схема может использоваться как конкретная разновидность схемы "Обобщить". Перенося одно или несколько условий исходной задачи в проверку, то есть, фактически временно отбрасывая их, мы получаем новую задачу, являющуюся естественным обобщением решаемой задачи. Иногда бывает удобно или более естественно схему "Перенести часть условий в проверку" интерпретировать как разновидность схемы "Переформулировать".

Опорная схема "Обратить функцию"

Задачи на обращение функций являются достаточно распространенными. Иногда возникает вопрос об обращении функций, заданных посредством алгоритмов. Пусть, например, относительно параметра x\in X решается уравнение вида f(x)=a при некотором известном рекурсивном алгоритме f и заданной величине a\in Y, где X и Y - множества. Тогда знание обратной для f рекурсивной функции g(y) (y\in Y) сразу же позволило бы решить исходное уравнение: f(x)=a, g(f(x))=g(a), x=g(a).

Поэтому умение "обратить" алгоритм является хотя и непростым, но достаточно полезным и эффективным подспорьем в решении задач рекурсивными методами. Реальное использование данной опорной схемы проводится так. Алгоритм решения исходной задачи нам неизвестен. Возможно, он не подходит по причине сложности или плохой эффективности. Однако для какой-либо из обратных для решаемой задачи удается построить алгоритм решения. В некоторых случаях простые рассуждения, приводящие к незначительным изменениям обратной задачи, позволяют получить конкретный искомый алгоритм.

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

Опорная схема "Найти родственника"

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

Рассмотрим пример, связанный с экзотическими средними. Пусть a0 и b0 - два положительных числа (a0 > b0). Составим их среднее арифметическое и среднее геометрическое. Продолжим этот процесс рекурсивно. Если числа an и bn уже построены, то определим an+1 и bn+1 следующим образом:

a_{n+1}=\frac{a_n+b_n}{2},\quad b_{n+1}=\sqrt{a_n\cdot b_n}\quad (n=0,1,...)

Можно показать, что a0>an>an+1>bn+1>bn>b0 (n=1,2,...). Откуда вытекает, что обе последовательности (an) и (bn) с двух разных сторон монотонно стремятся к общему пределу, который называют средним арифметико-геометрическим или экзотическим средним исходных чисел a0 и b0. Таким образом, при любом заданном n (n=0,1,2,...) числа (an) и (bn) служат приближениями сверху и снизу для среднего арифметико-геометрического a0 и b0. Для поиска экзотического среднего можно составить функцию, реализующую косвенную рекурсию. При этом параметризация, база и декомпозиция в явном виде приведены в задаче.

#include "stdafx.h"
#include <iostream>
using namespace std;
float Arifm(int n, float a, float b);
float Geom(int n, float a, float b);

int _tmain(int argc, _TCHAR* argv[]){
  int n;
  float a,b;
  do {
  printf ("a>0, a=");
  scanf("%f",&a);  
  printf ("b>0, b=");
  scanf("%f",&b); 
  printf ("n>0, n=");
  scanf("%d",&n); }
  while (a<=0 || b<=0 || n<=0);
  printf("Exotic: between %f and %f",
          Arifm(n,a,b),Geom(n,a,b));
  system("pause");
  return 0;
}

float Arifm(int n, float a, float b){ 
  if (n==0) return a;
  return (Arifm (n-1,a,b)+Geom(n-1,a,b))/2;
}
 
float Geom(int n, float a, float b){ 
  if (n==0) return b;
  return pow(double(Arifm (n-1,a,b)*Geom(n-1,a,b)),0.5);
}

Ключевые термины

"Использовать характеристическое свойство" – это опорная схема решения задачи рекурсивными способами, которая предполагает строить решение на общем свойстве, которым обладают представленные в задаче объекты.

"Найти родственника" – это опорная схема решения задачи рекурсивными способами, которая предполагает разделение задачи естественным образом на две или более вспомогательные родственные задачи так, что в совокупности, взаимно дополняя друг друга, они уже будут определять рекурсию.

"Обобщить" – это опорная схема решения задачи рекурсивными способами, которая предполагает решение задачи в общем виде с целью нахождения частного решения.

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

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

"Переформулировать" – это опорная схема решения задачи рекурсивными способами, которая предполагает перефразировать условие или построить математическую модель с целью обнаружить первоначально скрытую рекурсию.

"Увидеть" – это опорная схема решения задачи рекурсивными способами, которая предполагает использовать рекурсию, заданную условии в явном виде.

Введение вспомогательных элементов – это прием использования при решении задачи дополнительных параметров, явно не указанных в постановке задачи.

Моделирование – это замена исходной задачи ее моделью в виде математических описаний.

Опорные схемы рекурсивных вычислений – это подходы к выбору рекурсии как метода решения задач.

Преобразования задачи – это последовательные модификации решаемой задачи в цепочку эквивалентных задач, решение которых может быть получено более простым способом или уже известно.

Разбиение задачи на подзадачи – это выделение в задаче отдельных модулей, в дальнейшем реализуемых посредством функций.

Краткие итоги

  1. Решение задач рекурсивными способами не всегда явно следует из постановки задачи.
  2. Рекурсия не является универсальным методом построения алгоритмов. Ее следует рассматривать как альтернативный итерационному метод.
  3. Опорные схемы решения задач рекурсивными способами являются направлениями, задающими ход рассуждений при разработке триады.
  4. "Использовать характеристическое свойство" является опорной схемой решения задачи рекурсивными способами, которая предполагает строить решение на общем свойстве, которым обладают представленные в задаче объекты.
  5. "Найти родственника" является опорной схемой решения задачи рекурсивными способами, которая предполагает разделение задачи естественным образом на две или более вспомогательные родственные задачи так, что в совокупности, взаимно дополняя друг друга, они уже будут определять рекурсию.
  6. "Обобщить" является опорной схемой решения задачи рекурсивными способами, которая предполагает решение задачи в общем виде с целью нахождения частного решения.
  7. "Обратить функцию" является опорной схемой решения задачи рекурсивными способами, которая предполагает перейти от задачи к решению обратной для нее.
  8. "Перенести часть условий в проверку" является опорной схемой решения задачи рекурсивными способами, которая предполагает упрощение рекурсивных отношений за счет сведения задачи к эквивалентной подзадаче, отличающейся от исходной рядом условий.
  9. "Переформулировать" является опорной схемой решения задачи рекурсивными способами, которая предполагает перефразировать условие или построить математическую модель с целью обнаружить первоначально скрытую рекурсию.
  10. "Увидеть" является опорной схемой решения задачи рекурсивными способами, которая предполагает использовать рекурсию, заданную условии в явном виде.
< Лекция 35 || Лекция 36: 123 || Лекция 37 >
Денис Курбатов
Денис Курбатов
Владислав Нагорный
Владислав Нагорный

Подскажите, пожалуйста, планируете ли вы возобновление программ высшего образования? Если да, есть ли какие-то примерные сроки?

Спасибо!