Logo CitForum CITForum на CD Форумы Газета Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

24.05.2012

Google
WWW CITForum.ru

2.4. Рекурсия

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

Прямой (непосредственной) рекурсией является вызов функции внутри тела этой функции.


     int a()

     {.....a().....}

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

Например:


                    a(){.....b().....}

                    b(){.....c().....}

                    c(){.....a().....} .

Все функции a,b,c являются рекурсивными, так как при вызове одной из них, осуществляется вызов других и самой себя.

Рассмотрим задачу о Ханойских башнях. Имеются три стержня с номерами 1,2,3. На стержень 1 надето n дисков различного диаметра так, что они образуют пирамиду (см.рис.31). Написать программу для печати последовательности перемещений дисков со стержня на стержень, необходимых для переноса пирамиды со стержня 1 на стержень 3 при использовании стержня 2 в качестве вспомогательного. При этом за одно перемещение должен переноситься только один диск, и диск большего диаметра не должен помещаться на диск меньшего диаметра. Доказано, что для n дисков минимальное число необходимых перемещений равно 2^n-1.


Рис.31. Задача о Ханойских башнях.

Для решения простейшего случая задачи, когда пирамида состоит только из одного диска, необходимо выполнить одно действие - перенести диск со стержня i на стержень j, что очевидно (этот перенос обозначается i -> j). Общий случай задачи изображен на рисунке, когда требуется перенести n дисков со стержня i на стержень j, считая стержень w вспомогательным. Сначала следует перенести n-1 диск со стержня i на стержень w при вспомогательном стержне j, затем перенести один диск со стержня i на стержень j и, наконец, перенести n-1 диск из w на стержень j, используя вспомогательный стержень i. Итак, задача о переносе n дисков сводится к двум задачам о переносе n-1 диска и одной простейшей задаче. Схематически это можно записать так: T(n,i,j,w) = T(n-1,i,w,j), T(1,i,j,w), T(n-1,w,j,i).


                i     n-1     w    n-1       j

         +      |  -------->-+|+--------->+  |

      n-1|      |      I     |||    Ш     |  |

         +     / \           / \            / \

           +-/-----\-+     /     \      +-/-----\-+

         ==+----|----+===/=========\====+----|----+======

                +-------------->-------------+

                              П

          Рис.32. Схема решения задачи о Ханойских башнях.

Ниже приведена программа, которая вводит число n и печатает список перемещений, решающая задачу о Ханойских башнях при количестве дисков n. Используется внутренняя рекурсивная процедура tn(n,i,j,w), печатающая перемещения, необходимые для переноса n дисков со стержня i на стержень j с использованием вспомогательного стержня w при {i,j,w} = {1,3,2}.


   /*                    ханойские башни                    */

   #include 

   main()                                 /*   вызывающая   */

   {   void tn(int, int, int, int);       /*   функция      */

       int n;

       scanf(" %d",&n);

       tn(n,1,2,3);

    }



    void tn(int n, int i, int j, int w)   /*   рекурсивная  */

    {  if (n>1)                           /*   функция      */

        {  tn (n-1,i,w,j);

           tn (1,i,j,w);

           tn (n-1,w,j,i);

         }

        else printf(" \n %d -> %d",i,j);

        return ;

    }

Последовательность вызовов процедуры tn при m=3 иллюстрируется древовидной структурой на рис.33. Каждый раз при вызове процедуры tn под параметры n, i, j, w выделяется память и запоминается место возврата. При возврате из процедуры tn память, выделенная под параметры n, i, j, w, освобождается и становится доступной память, выделенная под параметры n, i, j, w предыдущим вызовом, а управление передается в место возврата.


Рис.33. Последовательность вызовов процедуры tn.

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

Предположим, что имеется ситуация:


     main()             /* вызывающая  функция  */

     { ...  f() ...}

     f()                /* рекурсивная функция  */

     { ...  f() ...}

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

Пусть рекурсивная функция f имеет параметры Р1,Р2,...,Рs, внутренние переменные V1,V2,...,Vt и в функциях main и f имеется k обращений к функции f. Для удаления такой функции требуются следующие дополнительные объекты:

- переменные AR1,AR2,...,ARs, содержащие значения фактических параметров при вызове функции f (типы переменных должны соответствовать типам параметров Р1,Р2,...,Рs);

- переменная rz для вычисляемого функцией f результата (тип переменных совпадает с типом возвращаемого значения функции f);

- стек, содержащий в себе все параметры и все внутренние переменные функции f, а также переменную lr типа int, для хранения точки возврата, и переменную pst типа указатель, для хранения адреса предыдущего элемента стека;

- указатель dl для хранения адреса вершин стека;

- промежуточный указатель u для операций над стеком;

- k новых меток L1,...,Lk для обозначенных точек возврата;

- метка jf, используемая для обхода модифицированного тела функции f;

- промежуточная переменная l типа int для передачи номера точки возврата.

Чтобы получить эквивалентную нерекурсивную программу без функции f, необходимо выполнить следующие действия:

1. Убрать объявление функции f в функцию main;

2. Добавить в функции main объявления переменных AR1,AR2,...,ARs,RZ, объявления стека ST и указателей dl и u:


      typedef struct st { P1;P2;...;Ps;V1;V2;...;Vt;

                          int lr;  struct st *pst  }  ST;

      ST *dl=NULL, *u;

3. Модифицировать тело функции f во фрагмент программы. Для этого следует:

а) удалить заголовок функции f;

б) объявления параметров и внутренних переменных и заменить фрагментом:


               goto jf;

            f: a=malloc(sizeof(ST));

               a->P1=AR1; a->P2=AR2; ... ;a->Ps=ARs;

               a->lr=l; a->pst=dl;  dl=a;

в) в конце функции f поставить метку JF, а все обращения к формальным аргументам заменить обращением, к соответствующим элементам стека;

г) вместо каждого оператора return(y) в функции f записать фрагмент:


               RZ=y; l=dl->lr;

               a=dl; dl=a->pst; free(a);

               switch(l)

               { case 1: goto L1;

                 case 2: goto L2;

                        ...

                 case k: goto Lk;

               }

4. Каждый i-тый вызов функции f (как в вызывающей функции, так и в теле функции f) вида V=f(A1,A2,...,As) заменить фрагментом программы :


     AR1=A1; AR2=A2; ... ; ARs=As;  l=i;  goto f;

     Li:  V=RZ;

где l=i обозначает l=1 при первом обращении к функции f, l=2 при втором обращении и т.д. Нумерация обращений может быть выполнена в произвольном порядке и требуется для возвращения в точку вызова с помощью операторов switch и goto Li; (где Li есть L1 при первой замене, Li есть L2 при второй замене и т.д.)

5. Вставить модифицированное тело функции f в конце функции main.

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


              + X+1,                 если N=0

              | X,                   если N=1,  Y=0,

              | 0,                   если N=2,  Y=0,

    A(N,X,Y)= | 1,                   если N=3,  Y=0,

              | 2,                   если N=>4, Y=0,

              + A(N-1,A(N,X,Y-1),X), если N#0,  Y#0;



     где N,X,Y - целые неотрицательные числа.

Следующая программа вычисляет функцию Аккермана с использованием рекурсивной функции ackr и вспомогательной функции smacc:


       /*      рекурсивное  вычисление функции Аккермана     */

       # include 

       main ()                           /*  вызывающая     */

       {   int x,y,n,t;                  /*  функция         */

           int ackr(int, int, int);

           scanf("%d %d %d",&n,&x,&y);

           t=ackr(n,x,y);

           printf("%d",t);

       }

      int smacc( int n,int x )          /*  вспомогательная  */

      {   switch (n)                    /*   функция         */

           {  case 0:  return(x+1);

              case 1:  return (x);

              case 2:  return (0);

              case 3:  return (1);

              default: return (2);

            }

      }

      int ackr( int n, int x, int y)    /*  рекурсивная      */

      {  int z;                         /*  функция          */

         int smacc( int,int);

         if(n==0 || y==0)  z=smacc(n,x);

         else { z=ackr(n,x,y-1);        /*  рекурсивные      */

                z=ackr(n-1,z,x);  }     /*  вызовы ackr(...) */

         return z;

      }

Модифицируя функции main и ackr в соответствии с изложенным методом получим следующую программу:


      /*       Эквивалентная нерекурсивная программа       */

      /*       для вычисления функции Аккермана            */

     #include 

     #include 

     int main()

     {  typedef struct st

        { int i,j,k,z,lr;

          struct st *pst;

        } ST;

        ST *u, *dl=NULL;

        int l,x,y,n;

        int  smacc(int,int);

        int an,ax,ay,rz,t;

        scanf("%i %i %i",&n,&x,&y);



        an=n;ax=x;ay=y;l=1;       /*  -   замена вызова    -  */

        goto ackr;                /*     t=ackr(n,x,y);       */

    l1: t=rz;                     /*  -  -  -  -  -  -  -  -  */



        printf("\n %d ",t);

        goto jackr;



        /*    начало фрагмента заменяющего функцию  ackr      */

    ackr:

        u=( ST *) malloc( sizeof (ST) );

        u->i=an;

        u->j=ax;

        u->k=ay;

        u->lr=l;

        u->pst=dl;

        dl=u;

        if (an==0||ay==0)

        dl->z=smacc(an,ax);

        else

             {

                an=dl->i;         /*  -   замена вызова    -  */

                ax=dl->j;         /*                          */

                ay=dl->k-1;       /*     z=ackr(n,x,y-1);     */

                l=2;              /*                          */

                goto ackr;        /*                          */

           l2:  dl->z=rz;         /*  -  -  -  -  -  -  -  -  */



                an=dl->i-1;       /*  -   замена вызова    -  */

                ax=rz;            /*                          */

                ay=dl->j;         /*     z=ackr(n-1,z,x);     */

                l=3;              /*                          */

                goto ackr;        /*                          */

           l3:  dl->z=rz;         /*  -  -  -  -  -  -  -  -  */

             }

        rz=dl->z;                 /*  -  -  -  -  -  -  -  -  */

        an=dl->i;                 /*                          */

        ax=dl->j;                 /*       замена             */

        ay=dl->k;                 /*                          */

        l=dl->lr;                 /*       оператора          */

        u=dl;                     /*                          */

        dl=u->pst;                /*       return z ;         */

        free(u);                  /*                          */

        switch(l)                 /*                          */

             {  case 1: goto l1;  /*                          */

                case 2: goto l2;  /*                          */

                case 3: goto l3;  /*                          */

             }                    /*  -  -  -  -  -  -  -  -  */

     jackr:

     }

     int smacc( int n,int x )      /* вспомогательная функция */

     {  switch (n)

             { case 0:  return(x+1);

               case 1:  return (x);

               case 2:  return (0);

               case 3:  return (1);

               default: return (2);

             }

     }

[ Назад | Оглавление ]

Подписка на новости CITForum.ru

Новые публикации:

19 мая

  • Прозрачный механизм удаленного обслуживания системных вызовов

  • Система моделирования Grid: реализация и возможности применения

    Газета:

    Майкл Стоунбрейкер:

  • Ошибки в системах баз данных, согласованность "в конечном счете" и теорема CAP

  • Дискуссия по поводу "NoSQL" не имеет никакого отношения к SQL

    29 апреля

  • Материалы конференции "Корпоративные Базы Данных-2010"

  • Разные облики технологии баз данных (отчет о конференции)

    14 апреля

  • MapReduce: внутри, снаружи или сбоку от параллельных СУБД?

  • Научные вызовы технологиям СУБД

    Обзоры журнала Computer:

    31 марта

  • Рационализация согласованности в "облаках": не платите за то, что вам не требуется

  • Взаимные блокировки в Oracle

  • Архитектура среды тестирования на основе моделей, построенная на базе компонентных технологий

  • Объектное представление XML-документов

    Газета:

  • Microsoft для российских разработчиков: практика с элементами фундаментальности

    10 марта

  • HadoopDB: архитектурный гибрид технологий MapReduce и СУБД для аналитических рабочих нагрузок

  • Классификация OLAP-систем вида xOLAP

  • BGP. Три внешних канала. Балансировка исходящего и входящего трафиков

    Газета:

  • Что мы знаем об iPhone 4G?

    17 февраля

  • MapReduce и параллельные СУБД: друзья или враги?

  • Объектно-ориентированное программирование в ограничениях: новый подход на основе декларативных языков моделирования данных

  • Системологический подход к декомпозиции в объектно-ориентированном анализе и проектировании программного обеспечения

    Газета:

  • Эволюция Wine

    3 февраля

  • Дом на песке

  • Реальное переосмысление "формальных методов"

  • Интервью с Найджелом Пендзом

    Газета:

  • iPad. Первый взгляд на долгожданный планшет от Apple

  • Я не верю в iPad

    20 января

  • SQL/MapReduce: практический подход к поддержке самоописываемых, полиморфных и параллелизуемых функций, определяемых пользователями

  • Данные на лету: как технология потокового SQL помогает преодолеть кризис

    Обзоры журнала Computer:

    2 декабря

  • Сергей Кузнецов. Год эпохи перемен в технологии баз данных

    18 ноября

  • Генерация тестовых программ для подсистемы управления памятью микропроцессора

  • Сравнительный анализ современных технологий разработки тестов для моделей аппаратного обеспечения

    Все публикации >>>


  • IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

    Информация для рекламодателей PR-акции, размещение рекламы — тел. +7 495 6608306, ICQ 232284597 Пресс-релизы — pr@citforum.ru
    Послать комментарий
    Информация для авторов

    Редакция раздаёт котят!

    Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
    Copyright © 1997-2000 CIT, © 2001-2009 CIT Forum
    Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...