Опубликован: 19.02.2009 | Уровень: специалист | Доступ: платный
Лекция 6:

Рекурсивные методы

< Лекция 5 || Лекция 6: 123 || Лекция 7 >
Аннотация: В данной лекции внимание уделено рекурсивным методам. Приведены практические примеры.

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

Из курса математики известно, что 0!=1!=1, n!=1*2*3…*n. С другой стороны n!=(n-1)!*n. Таким образом, известны два частных случая параметра n, а именно n= 0 и n=1, при которых мы без каких-либо дополнительных вычислений можем определить значение факториала. Во всех остальных случаях, то есть для n>1, значение факториала может быть вычислено через значение факториала для параметра n-1. Таким образом, рекурсивный метод будет иметь вид:

{
   static long F(int n)  //рекурсивный метод
   {
      if (n==0 || n==1) 
       return 1;    //нерекурсивная ветвь
       else return n*F(n-1);  //шаг рекурсии - повторный вызов метода с другим параметром
    }

    static void Main()
    {
      Console.Write("n=");
       int n =int.Parse( Console.ReadLine());
       long f=F(n); //нерекурсивный вызов метода F
       Console.WriteLine("{0}!={1}",n, f); 
      }
}

Рассмотрим работу описанного выше рекурсивного метода для n=3.


Первый вызов метода осуществляется из метода Main, в нашем случае командой f=F(3). Этап вхождения в рекурсию обозначим жирными стрелками. Он продолжается до тех пор, пока значение переменной n не становится равной 1. После этого начинается выход из рекурсии (тонкие стрелки). В результате вычислений получается, что F(3)=3*2*1.

Рассмотренный вид рекурсии называют прямой. Метод с прямой рекурсией обычно содержит следующую структуру:

if (<условие>)  
<оператор>; 
else <вызов данного метода с другими параметрами>;

В качестве <условия> обычно записываются некоторые граничные случаи параметров, передаваемых рекурсивному методу, при которых результат его работы заранее известен, поэтому далее следует простой оператор или блок, а в ветви else происходит рекурсивный вызов данного метода с другими параметрами.

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

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

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

Пример 1:Найти сумму цифр числа А.

Известно, что любое натуральное число A=an an-1... a1 a0, где an an-1... a1 a0 - цифры числа, можно представить следующим образом:

A=an an-1... a1 a0 = A=an*10n + an-1*10n-1 + ... a1*101 + a0*100 = ((...((an*10 + an-1)*10+ an-2)*10...)*10 + a1)*10 + a0

Например, число 1234 можно представить как:

1234 = 1*103 + 2*102 + 3*101 + 4*100 = ((1*10 + 2)*10 + 3)*10 + 4

Из данного представления видно, что получить последнюю цифру можно, если найти остаток от деления числа на 10. В связи с этим для разложения числа на составляющие его цифры можно использовать следующий алгоритм:

  1. Находим остаток при делении числа А на 10, т.е. получаем крайнюю правую цифру числа.
  2. Находим целую часть числа при делении A на 10, т.е. отбрасываем от числа A крайнюю правую цифру.
  3. Если преобразованное A > 0, то переходим на пункт 1. Иначе число равно нулю и отделять от него больше нечего.

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

С другой стороны, сумму цифр числа 1234 можно представить следующим образом sum(1234)=sum(123)+4=(sum(12)+3)+4=(((sum(1)+2)+3)+4)=(((sum(0)+1)+2)+3)+4. Таким образом, если А=0, то сумма цифр числа также равна нулю, т.е. sum=0. В противном случае сумму цифр числа A можно представить рекуррентным соотношением sum(A)=sum(A/10)+A%10. Полученное рекуррентное соотношение будем использовать при разработке рекурсивного метода.

class Program
  {
    static long Sum(long a) //нерекурсивный метод
    {
      long sum=0;
      while (a>0) //пока a больше нуля
      {
        sum+=a%10;  //добавляем к сумме последнюю цифру числа а
        a/=10;   //отбрасываем от числа а последнюю цифру
      }
      return sum;  //возвращаем в качестве результата сумму цифр числа a
    }

    static long SumR(long a) //рекурсивный метод
    {
      if (a==0) //если a =0, то
                 return 0; // возвращаем 0
      else return SumR(a/10)+ a%10; //иначе обращаемся к рекуррентному соотношению
    }

    static void Main()
    {
      Console.Write("n=");
      long n=long.Parse(Console.ReadLine());
      Console.WriteLine("Нерекурсивный метод: "+Sum(n));
      Console.WriteLine("Рекурсивный метод: "+SumR(n));
    }  
  }
}
Задание. Изменить методы так, чтобы на экран выводилось количество цифр в числе n.

Пример 2:вычислить n-ный член последовательности Фиббоначи.

Первые два члена последовательности Фиббоначи равны 1, остальные получаются по рекуррентной формуле an=an-1+an-2.

class Program
  {
    static int Fb(int n) //нерекурсивный алгоритм
    {
      int a, a1=1, a2=1;
      if (n==1||n==2) return 1;
      else 
      {
        for (int i=2; i<=n; ++i) 
        {
          a=a1+a2;
          a1=a2;
          a2=a;
        }
        return a1;
      }
    }

    static int FbR(int n) //рекурсивный алгоритм
    {
      if (n==1 || n==2 )return 1;
        else return FbR(n-1)+FbR(n-2);
    }

    static void Main()
    {
      Console.Write("n=");
      int n=int.Parse(Console.ReadLine());
      Console.WriteLine("Нерекурсивный метод: "+Fb(n));
      Console.WriteLine("Рекурсивный метод: "+FbR(n));
    }    
  }
Задание. Изменить методы так, чтобы на экран выводилась сумма n элементов последовательности Фиббоначи.

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

< Лекция 5 || Лекция 6: 123 || Лекция 7 >
Sahib Dadashev
Sahib Dadashev
Россия
Роман Казимирчик
Роман Казимирчик
Беларусь