Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Рекурсивная функция


Автор: observateur 10.6.2006, 12:48
Заранее спасибо тем кто ответить...Самостоятолна изучаю C++. помогите пожалоста  понять как работает Рекурсивная функция. Уменя есть книга Х.М.Дейтель "Как программировать на С++". Все равно трудно понять суть рекурсии. smile  

Автор: Xenon 10.6.2006, 12:58
Что значит КАК? Ну функция из самой себя вызывает саму себя с новым аргументом smile Удобно использовать для математических вычислений, в конце концов для вычисления факториала smile Единственное, нужно следить, чтобы проверялось условие и этот цикл завершился когда-нибудь, а не стал бесконечным smile
Вот нахождение факториала простым способом
Код

int fact(int n)
{
    int result=1;
    for (int i=1;i<=n;i++) 
    {
        result=result*i;
    }
    return result;


Вот пр помощи рекрусси:
Код

int fact(int n)
{
    if (n==1) // факториал единицы равен одному
    {
        return 1;
    }
    else           
    {
        return n*fact(n-1);
    }
}  
  

Автор: observateur 10.6.2006, 13:04
Я знаю условия цикла рекурсивный функции. мне надо схема ее рабаты. Точнее схематический как она работает. спасибо за вашу терпению 

Автор: AlanG 10.6.2006, 13:27
Цитата

Я знаю условия цикла рекурсивный функции. мне надо схема ее рабаты. Точнее схематический как она работает. спасибо за вашу терпению  

Так все спрашивают, когда начинаю smile . Это не нужно, и вообще задумыватся над этим не надо, поверь smile . Логика проста, как два мегабайта скачать smile 
А думать о схеме не надо, вот как это происходит



Код

int fact(int n)
{
    if (n==1) // факториал единицы равен одному
    {
        return 1;
    }
    else           
    {
        return n*fact(n-1); //сдесь fact(n-1) - это вызов функции fact(), причем заметь что ему (функции fact) 
//передается агрумент в виде числа равного (n-1) и все, вся рекурсия
 //А то что return (возврат), он сразу не происходит, сначала всеже вызывается 
//функция, а вот когда n cтанет равным 1, последнему вызову возвратится результат, 
//этот результат перемножается с n (n*fact(n-1))
 
    }



Вообще все вызовы сохраняются в стеке, и каждому вызову присваивается значение с помощбю return
  

Автор: Pete 10.6.2006, 13:42
XenonSk, 0! = 1.
Код
int fact (int n) {
  if (n == 0) return 1;
  return (n * fact (n - 1));
}


На примере того же факториала:
Мы знаем рекуррентное соотношение (которое, кстати, лежит в основе каждой рекурсивной функции):
fact (n) = n * fact (n - 1).
Вот мы это и реализуем: в предположении, что наша функция вычисляет факториал, пишем, что "n! = n(n - 1)!".
При этом мы уверены, что (n - 1)! будет вычислен правильно. На основе этого получаем то, что надо.     

Автор: AlanG 10.6.2006, 13:54
Цитата

XenonSk, 0! = 1.

А ноль для интереса прибавил, или чтобы начинающего запутать smile 

Цитата

На примере того же факториала:
Мы знаем рекуррентное соотношение (которое, кстати, лежит в основе каждой рекурсивной функции):
fact (n) = n * fact (n - 1).
Вот мы это и реализуем: в предположении, что наша функция вычисляет факториал, пишем, что "n! = n(n - 1)!".
При этом мы уверены, что f (n - 1). На основе этого получаем то, что надо.  

Перевод будет? smile 

 

Автор: Xenon 10.6.2006, 14:03
Pete, Ну и? smile 

Автор: Pete 10.6.2006, 14:12
Поправил) 

Автор: Xenon 10.6.2006, 14:30
Млин, я 0!=1 прочитал, как : "Нуль не равен единице" ...  smile  
Тогда уж:
Код

int fact(int n)    
{    
    if (n==0 || n==1)  
    {    
        return 1;    
    }    
    else            
    {    
        return n*fact(n-1);    
    }    
}  
  

Автор: Pete 10.6.2006, 14:37
Это не что иное, как оптимизация. Если говорить бо оптимизации, то вычисление факториала с помощью рекурсии гораздо дороже итеративного варианта и выйгрыша почти никакого. 

Автор: observateur 10.6.2006, 16:12
Спасибо всем кто отвечал. Постараюс понять. 

Автор: MAKCim 10.6.2006, 17:27
Цитата

и выйгрыша почти никакого.  

я бы сказал вообще никакого  smile  

Автор: Void 10.6.2006, 17:45
Цитата(Pete @  10.6.2006,  16:37 Найти цитируемый пост)
сли говорить бо оптимизации, то вычисление факториала с помощью рекурсии гораздо дороже итеративного варианта и выйгрыша почти никакого.  

Хвостовая рекурсия эквивалентна итерации (точнее, наоборот). В приведенном коде рекурсия не хвостовая, но легко к ней сводится. Так что разница в способе вычисления тут скорее не алгоритмическая, и все зависит от прозорливости оптимизатора smile По крайней мере, явную хвостовую рекурсию приличные компиляторы C++ сворачивают уже давно. 

Автор: Pete 10.6.2006, 18:07
Цитата(MAKCim @  10.6.2006,  17:27 Найти цитируемый пост)
я бы сказал вообще никакого

Небольшой есть.
Цитата(Void @  10.6.2006,  17:45 Найти цитируемый пост)
Хвостовая рекурсия эквивалентна итерации (точнее, наоборот).

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

Автор: Void 10.6.2006, 18:11
Цитата(Pete @  10.6.2006,  20:07 Найти цитируемый пост)
Время работы рекурсивной программы никогда не будет эквивалентно ее итерационной версии (именно с прикладной точки зрения), ибо сам процесс вызова функции очень дорог, не говоря уж об ограниченности стека и т.п.

Есть алгоритмы, которые рекурсивны по своей природе, и для их итеративной реализации придется организовывать собственный стек. Сомнительный выигрыш.
Цитата(Pete @  10.6.2006,  20:07 Найти цитируемый пост)
На такие вещи, по-моему, нельзя надеяться.  

В С++ — нельзя, согласен.
 

Автор: Pete 10.6.2006, 18:19
Цитата(Void @  10.6.2006,  18:11 Найти цитируемый пост)
Есть алгоритмы, которые рекурсивны по своей природе, и для их итеративной реализации придется организовывать собственный стек.

Скажем так: любой рекурсивный алгоритм из http://www.google.ru/search?hl=ru&q=%22%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1%8B%3A+%D0%BF%D0%BE%D1%81%D1%82%D1%80%D0%BE%D0%B5%D0%BD%D0%B8%D0%B5+%D0%B8+%D0%B0%D0%BD%D0%B0%D0%BB%D0%B8%D0%B7%22&btnG=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA&lr=, насколько я их помню, можно переписать в итерационном виде с увеличением быстродействия. Если ты имеешь в виду что-то, быть может, экзотическое, то прошу в студию.
 smile  

Автор: Void 10.6.2006, 18:22
Pete, ничего экзотического, перепиши обычную быструю сортировку (Хоара), не создавая собственного стека.
И сравни быстродействие эмуляции стека с явной рекурсией. Такой вариант, возможно, окажется быстрее, но ненамного. И даже в этом я не уверен. 

Автор: Pete 10.6.2006, 18:44
Хм.. Согласен. Пока не встречал ее итерационной реализации без стека. Сравнение этих алгоритмов в скорости, наверное, уже больше зависит от системы. Конечно, отсутствие объявления новых переменных на каждом уровне рекурсии придаст определенное ускорение, хотя, кто знает...
(ЗЫ: мы пока от темы не ушли далеко? smile )  

Автор: Xenon 10.6.2006, 20:25
Ну мне кажется тут такой же "большой" прирост в скорости, что и при использовании
Код
 
int x=5;
x=x<<1;

Вместо
Код

int x=5;
x*=2
 

Автор: kirjanov 10.6.2006, 22:24
XenonSk
конечно, прирост производительности явно не будет большим. Может твой компилятор и сгенерирует эквивалентный код, как в верхней инструкции. Что может быть более показательным сейчас, как использование интерпретируемых языков.... 

Автор: Xenon 10.6.2006, 22:34
kirjanov, То, что кинул я считается не одним и тем же ... Вообще говорят, что часто побитовый сдвиг дает хороший прирост smile 

Автор: Void 10.6.2006, 23:13
Цитата(XenonSk @  11.6.2006,  00:34 Найти цитируемый пост)
Вообще говорят, что часто побитовый сдвиг дает хороший прирост 

Я не знаю компилятора, настолько тупого, чтобы умножение или деление на константу он не превратил в битовый сдвиг.
И, кстати, в случае знакововых чисел можно сильно огрести, использовав сдвиг вправо вместо деления:
Код
#include <cstdio>
#include <cstdlib>

int main() {
    int x = -1;
    printf("%i %i", x >> 1, x / 2);
}

Цитата
-1 0


И еще:
Код
#include <cstdio>
#include <cstdlib>

int main() {
    int x = rand();
    int y = rand();
    x <<= 1;
    y *= 2;
    printf("%i %i", x, y);
}

Код
;;;    int x = rand();
        call      _rand                                         ;5.10
$B1$8:
        mov       ebx, eax                                      ;5.10
$B1$2:
;;;    int y = rand();
        call      _rand                                         ;6.10
$B1$3:
;;;    x <<= 1;
        add       ebx, ebx                                      ;7.2
;;;    y *= 2;
        add       eax, eax                                      ;8.2

Это Intel C++ 9.0 в режиме максимальной оптимизации.
Не оптимизируйте, и не оптимизируемы будете smile 

Автор: Xenon 10.6.2006, 23:15
Void, просто говорю, что в справочнике написано, а об "вылезании" там тоже писали smile 

Автор: MAKCim 11.6.2006, 09:45
Цитата

ничего экзотического, перепиши обычную быструю сортировку (Хоара), не создавая собственного стека.
И сравни быстродействие эмуляции стека с явной рекурсией. Такой вариант, возможно, окажется быстрее, но ненамного. И даже в этом я не уверен. 

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

Автор: Void 11.6.2006, 16:24
Цитата(MAKCim @  11.6.2006,  11:45 Найти цитируемый пост)
насколько я знаю при рекурсивном вызове в стек заносится все окружение (локальные переменные) и адрес возврата

В стек заносится лишь адрес возврата и параметры вызываемой функции.
Да, вызываемая функция выделяет место на стеке для своих локальных переменных, но это — одна инструкция изменения регистра стека.
Цитата(MAKCim @  11.6.2006,  11:45 Найти цитируемый пост)
при быстрой организации нашего стека (например статический массив)

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

Автор: MAKCim 12.6.2006, 09:41
Цитата

В стек заносится лишь адрес возврата и параметры вызываемой функции.

я это и имел в виду, не так выразился
Цитата

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

под статическим массивом я подразумевал массив на стеке (размер известен в compile-time)
как пример можно взять рекурсивную функцию
Код

void F(int a)
{
    if (a==63) return;
    F(a+1);
    std::cout<<a<<std::endl;
}

и эквивалентную ее итеративную с эмуляцией стека
Код

void G(int a)
{
    int stack[63];
    char index=0;
    while (index!=63)
    {
        stack[index]=a;
        ++index;
        ++a;
    }
    while (--index) std::cout<<stack[index]<<std::endl;
    std::cout<<stack[index]<<std::endl;
}

и вызов
Код

int main()
{
    F(0);
    G(0);
    return 0;
}

тут итеративная выигрывает в используемой памяти (261 против 512 байт) и по количеству команд процессора
(326 против 575 (вывод считаем оной командой; командную реализацию while и if не рассматриваем))
так как любая рекурсивная программа сводится к итеративной, то, как показывает пример, при правильной ее организации она оказывается более эффективной  

Автор: Void 14.6.2006, 16:36
Эх, люблю я бенчмарки проводить smile Вот и в этот раз не выдержал и написал тест. Все ту же быструю сортировку в двух вариантах: явная рекурсия и эмуляция через стек.
Код
#include <iostream>
#include <algorithm>
#include <functional>
#include <utility>
#include <vector>
#include <climits>
#include <cstdlib>
#include <ctime>


// Возвращает медиану из первого, последнего и серединного элемента.
template <typename Iterator, typename Comparer>
inline Iterator find_pivot(Iterator begin, Iterator end,
                           const Comparer &cmp) {
    Iterator middle = begin + (end - begin) / 2;
    if (cmp(*begin, *middle) && cmp(*(end - 1), *begin)
        || !cmp(*begin, *middle) && !cmp(*(end - 1), *begin))
        return begin;
    else if (cmp(*(end - 1), *middle) && cmp(*begin, *(end - 1))
        || !cmp(*(end - 1), *middle) && !cmp(*begin, *(end - 1)))
        return end - 1;
    return middle;
}

template <typename Iterator, typename Comparer>
inline Iterator partition(Iterator begin, Iterator end, Iterator pivot,
                          const Comparer &cmp) {
    typename std::iterator_traits<Iterator>::value_type pivotValue(*pivot);
    std::iter_swap(pivot, end - 1);
    Iterator p = std::partition(begin, end - 1, std::bind2nd(cmp, pivotValue));
    iter_swap(p, end - 1);
    return p;
}

template <typename Iterator, typename Comparer>
void rec_qsort(Iterator begin, Iterator end,
               const Comparer &cmp = Comparer()) {
    if (end - begin > 1) {
        Iterator pivot = find_pivot(begin, end, cmp);
        pivot = partition(begin, end, pivot, cmp);
        if (pivot - begin > end - pivot) {
            rec_qsort(begin, pivot, cmp);
            rec_qsort(pivot + 1, end, cmp);
        } else {
            rec_qsort(pivot + 1, end, cmp);
            rec_qsort(begin, pivot, cmp);
        }
    }
}

template <typename Iterator>
void rec_qsort(Iterator begin, Iterator end) {
    rec_qsort(begin, end,
        std::less<typename std::iterator_traits<Iterator>::value_type>());
}

template <typename Iterator, typename Comparer>
void qsort(Iterator begin, Iterator end, const Comparer &cmp = Comparer()) {
    // глубина рекурсии не превышает log_2(n), где n - длина сортируемой
    // последовательности
    std::pair<Iterator, Iterator> stack[
        sizeof(typename std::iterator_traits<Iterator>::difference_type) *
        CHAR_BIT];
    size_t top = 0;
    stack[top++] = std::make_pair(begin, end);
    while (top) {
        --top;
        begin = stack[top].first;
        end = stack[top].second;
        while (end - begin > 1) {
            Iterator pivot = find_pivot(begin, end, cmp);
            pivot = partition(begin, end, pivot, cmp);
            if (pivot - begin > end - pivot) {
                stack[top++] = std::make_pair(begin, pivot);
                begin = pivot + 1;
            } else {
                stack[top++] = std::make_pair(pivot + 1, end);
                end = pivot;
            }
        }
    }
}

template <typename Iterator>
void qsort(Iterator begin, Iterator end) {
    qsort(begin, end,
        std::less<typename std::iterator_traits<Iterator>::value_type>());
}


class timer {
    clock_t start_;
public:
    timer() : start_(clock()) { }

    double elapsed() {
        return 1.0 * (clock() - start_) / CLOCKS_PER_SEC;
    }

    void reset() {
        start_ = clock();
    }
};


int myrandom() {
    return rand() * (RAND_MAX + 1) + rand();
}


int main() {
    const size_t arraySize = 40000000;

    srand(time(NULL));
    std::vector<int> a(arraySize), b;
    std::generate(a.begin(), a.end(), myrandom);
    b = a;
    
    timer t;
    rec_qsort(a.begin(), a.end());
    std::cout << "Recursion:\t" << t.elapsed() << " sec" << std::endl;
    
    t.reset();
    qsort(b.begin(), b.end());
    std::cout << "No recursion:\t" << t.elapsed() << " sec" << std::endl;
}

Результаты:
GCC 3.4.5 (MinGW)
Цитата
Recursion:      7.797 sec
No recursion:   7.781 sec

Recursion:      7.797 sec
No recursion:   7.766 sec

Recursion:      7.812 sec
No recursion:   7.782 sec

Recursion:      7.797 sec
No recursion:   7.781 sec

Recursion:      7.797 sec
No recursion:   7.859 sec

VC 8.0:
Цитата
Recursion:      8.61 sec
No recursion:   8.719 sec

Recursion:      8.656 sec
No recursion:   8.766 sec

Recursion:      8.625 sec
No recursion:   8.781 sec

Recursion:      8.61 sec
No recursion:   8.765 sec

Recursion:      8.656 sec
No recursion:   8.735 sec

Вот такая забавная картина, не находите? smile
Так стоит ли заморачиваться с ручной эмуляцией стека ради пары процентов производительности, которые в зависимости от компилятора могут оказаться и на той, и на другой стороне? ИМХО, нет.
В данном случае можно было оценить верхний предел глубины рекурсии, однако выделяя под стек массив постоянного размера мы в любом случае рискуем молчаливо вылететь за его пределы. Используя же динамический массив мы рискуем свести на нет все призрачные преимущества эмуляции рекурсии. 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)