С Новым годом! Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.85/40: Рейтинг темы: голосов - 40, средняя оценка - 4.85
4 / 4 / 0
Регистрация: 26.10.2014
Сообщений: 105
1

Рекурсия vs цикл с точки зрения производительности

29.08.2015, 18:43. Показов 8131. Ответов 26
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Здравствуй, дорогой форум!
Написал алгоритм для отображения десятичных чисел в двоичной системе в двух вариантах: с использование рекурсии и цикла:

Первый вариант(используем рекурсию):
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
 
using namespace std;
 
int *Array; //Массив, в котором будут храниться остатки от каждого деления плюс частное от последнего деления.
int Counter; //Счётчик количества делений х на основание системы.
int Remain; //Остаток от деления.
int Index; //Переменная для хранения индекса массива.
int Temp; //Временная переменная
bool Condition = false; //Условие выхода из цикла.
 
void Bin(int x) //Сам алгоритм:
{
  while (Condition == false)
        {
          if (!Counter) //Если это первый вызов...
             {
               Temp = x; //Сохраняем значение х во временную переменную.
             }
          x = x / 2;
          ++Counter; //Считаем, сколько раз х делится на основание системы.
          if (x < 2) //Если частное стало меньше основания системы...
             {
               Array = new int[Counter + 1]; //Создаём массив нужного размера.
               Index = Counter; //Index указывает на последнюю ячейку массива.
               x = Temp; //Возвращаем х на место.
               Condition = true; //Выходим из цикла.
             }
        }
  Remain = x % 2; //Остаток.
  x = x / 2; //Частное.
  if (x < 2) //Частный случай: если частное стало меньше основания системы...
     {
       Array[0] = x; //Заполняем первые две ячейки массива.
       Array[1] = Remain; //Заполняем первые две ячейки массива.
       for (int i = 0; i <= Counter; i++)
           {
             cout << Array[i]; //Выводим содержимое массива на экран.
           }
       cout << "\n";    
       delete [] Array; //Освобождаем память, выделенную под массив.
       Array = NULL;
       Counter = 0; //Обнуляем счётчик для последующих вызовов функции.
       Condition = false; //Сбрасываем условие для последующих вызовов функции.   
     }
  else
     {
       Array[Index] = Remain; //Заполняем массив остатками от каждого деления, начиная с конца.
       --Index;
       Bin(x); //Используем рекурсию.
     }   
}
 
int main()
{
  int x;
  cout << "Set x:" << "\t";
  cin >> x;
  Bin(x);
  return 0;
}
Второй вариант(рекурсию заменяем циклом):

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>
 
using namespace std;
 
int *Array; //Массив, в котором будут храниться остатки от каждого деления плюс частное от последнего деления.
int Counter; //Счётчик количества делений х на основание системы.
int Remain; //Остаток от деления.
int Index; //Переменная для хранения индекса массива.
int Temp; //Временная переменная
bool Condition = false; //Условие выхода из цикла.
 
void Bin(int x) //Сам алгоритм:
{
  while (Condition == false)
        {
          if (!Counter) //Если это первый вызов...
             {
               Temp = x; //Сохраняем значение х во временную переменную.
             }
          x = x / 2;
          ++Counter; //Считаем, сколько раз х делится на основание системы.
          if (x < 2) //Если частное стало меньше основания системы...
             {
               Array = new int[Counter + 1]; //Создаём массив нужного размера.
               Index = Counter; //Index указывает на последнюю ячейку массива.
               x = Temp; //Возвращаем х на место.
               Condition = true; //Выходим из цикла.
             }
        }
  for ( ; ; )
      {
        Remain = x % 2; //Остаток.
        x = x / 2; //Частное.
        if (x < 2) //Если частное стало меньше основания системы...
           {
             Array[0] = x; //Заполняем первые две ячейки массива.
             Array[1] = Remain; //Заполняем первые две ячейки массива.
             for (int i = 0; i <= Counter; i++)
                 {
                   cout << Array[i]; //Выводим содержимое массива на экран.
                 }
             cout << "\n";    
             delete [] Array; //Освобождаем память, выделенную под массив.
             Array = NULL;
             Counter = 0; //Обнуляем счётчик для последующих вызовов функции.
             Condition = false; //Сбрасываем условие для последующих вызовов функции.
             break; //Завершаем цикл.  
           }
        else
           {
             Array[Index] = Remain; //Заполняем массив остатками от каждого деления, начиная с конца.
             --Index;
           }
      }
}
 
int main()
{
  int x;
  cout << "Set x:" << "\t";
  cin >> x;
  Bin(x);
  return 0;
}
В общем-то, с точки зрения результата, разницы нет никакой. Но меня здесь интересует вопрос производительности. Я могу ошибаться, но моя нубская логика подсказывает мне, что многократный вызов функции - дело более ресурсоёмкое, чем выполнение цикла. Так стоит ли отказываться от рекурсии в пользу цикла, если это возможно?
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
29.08.2015, 18:43
Ответы с готовыми решениями:

Выбор встроенной базы данных с точки зрения производительности
Нужно создать приложение на C# умеющее работать с базой данных. В качестве СУБД нужно использовать...

Как лучше реализовать сервер с точки зрения производительности?
Здравствуйте, хочу потренироваться в сетевом программировании и вот стал вопрос: Как лучше...

Drawingvisual или Usercontrol: какой вариант будет реализовать правильней с точки зрения производительности
передомной стоит задача написать программу.чтобы было можно рисовать некоторые uml...

Если два метода выполняют одно и то же - с точки зрения программы, но разное - с точки зрения логики?
void killCh(BCell cKiller, BCell cVictim){ cVictim.setChessman(cKiller.getChessman()); ...

26
1375 / 519 / 72
Регистрация: 21.07.2015
Сообщений: 1,304
29.08.2015, 19:01 2
Цитата Сообщение от Alexey104 Посмотреть сообщение
Первый вариант
Цитата Сообщение от Alexey104 Посмотреть сообщение
Второй вариант

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
void print_bin(unsigned int val)
{
    const int int_bits = sizeof(unsigned int) * 8;
    unsigned int mask;
    for(mask = (1 << (int_bits - 1)); mask != 1; mask >>= 1)
        if( (val & mask) != 0)
            break;
    for(; mask != 0; mask >>= 1)
         if( (val & mask) != 0)
            cout << '1';
         else
            cout << '0';
}
Добавлено через 2 минуты
Цитата Сообщение от Alexey104 Посмотреть сообщение
но моя нубская логика подсказывает мне, что многократный вызов функции - дело более ресурсоёмкое, чем выполнение цикла
Вообще - да, вызов процедуры занимает время (особенно если у функции куча входных параметров). Но тут еще все зависит от того как "развернуть" рекурсию, можно так, что трудоемкость алгоритма станет другой.
1
4 / 4 / 0
Регистрация: 26.10.2014
Сообщений: 105
29.08.2015, 19:28  [ТС] 3
Цитата Сообщение от shmkv Посмотреть сообщение
Хех...
Да, такие мы маньяки, лёгкие пути - это не для нас!
Если серьёзно, то ваш код, конечно, выглядит куда более лаконично, чем мой том войны и мира, но мне он непонятен с моими текущими знаниями языка. Написал, как смог.)
В любом случае на мой вопрос вы ответили, так что ловите спасибо!

shmkv
P.S.
А как называется такой оператор: ">>="? По символам гугл ничего не находит. Хотел бы понять ваш код.
0
806 / 533 / 158
Регистрация: 27.01.2015
Сообщений: 3,017
Записей в блоге: 1
29.08.2015, 19:32 4
Alexey104, ссылаясь на материал из книги Дейтелов по Си++, могу сказать что, итеративный (циклический) метод вычисления, как правило, менее ресурсоемкий => более быстрый. Но бывают такие случаи, когда решение задачи рекурсивным образом бывает проще с точки зрения объема кода и его отладки. Например, создание динамических структур данных по типу дерева.
0
1375 / 519 / 72
Регистрация: 21.07.2015
Сообщений: 1,304
29.08.2015, 19:36 5
Цитата Сообщение от Alexey104 Посмотреть сообщение
но мне он непонятен с моими текущими знаниями языка.
Так спрашивай что не понятно. Тут всего 2 цикла маленьких.
0
806 / 533 / 158
Регистрация: 27.01.2015
Сообщений: 3,017
Записей в блоге: 1
29.08.2015, 19:37 6
Цитата Сообщение от Alexey104 Посмотреть сообщение
А как называется такой оператор: ">>="?
Побитовый сдвиг вправо с присваиванием, как правило, такие конструкции заимствованы из чистого Си. Ими, по возможности, лучше не пользоваться в целях успешной переносимости программ.
1
196 / 197 / 120
Регистрация: 27.05.2011
Сообщений: 545
29.08.2015, 19:41 7
Цитата Сообщение от Alexey104 Посмотреть сообщение
А как называется такой оператор: ">>="? По символам гугл ничего не находит. Хотел бы понять ваш код.
a >>= b тоже самое, что и a = a >> b, только a вычисляется один раз.
Аналогично более популярное a += b.
1
1375 / 519 / 72
Регистрация: 21.07.2015
Сообщений: 1,304
29.08.2015, 19:48 8
Лучший ответ Сообщение было отмечено Alexey104 как решение

Решение

Цитата Сообщение от Alexey104 Посмотреть сообщение
А как называется такой оператор: ">>="
Двоичный сдвиг вправо с присвоением. x >>= n практически эквивалентно x = x / pow(2, n), если предположить, что pow возвращает целые числа (библиотечный возвращает вещественные).

Добавлено через 42 секунды
для x =>> 1 <=> x /= 2;

Добавлено через 53 секунды
Суть всего алгоритма : в первом цикле я ищу первый единичный бит слева, а во втором последовательно вывожу биты слева направо.
0
4 / 4 / 0
Регистрация: 26.10.2014
Сообщений: 105
29.08.2015, 19:55  [ТС] 9
Всем спасибо за участие!
Буду разбираться с ">>="...

Не по теме:

Цитата Сообщение от Ferrari F1 Посмотреть сообщение
ссылаясь на материал из книги Дейтелов...
Как раз дочитываю Либерти, которого все советуют сжечь(действительно, есть просто непростительные косяки в листингах), и собираюсь читать либо Дейтелов, либо Прата. Что скажете о книге Дейтелов, рекомендуете?

0
806 / 533 / 158
Регистрация: 27.01.2015
Сообщений: 3,017
Записей в блоге: 1
29.08.2015, 20:03 10
Alexey104, да, Дейтелы мне понравились. Для новичков достаточно полно описывают, но это не первая моя книга по Си++, поэтому в крайне редких случаях может быть иногда недостаточно понятно для человека, читающего впервые.

Добавлено через 3 минуты
Цитата Сообщение от Alexey104 Посмотреть сообщение
Буду разбираться с ">>=n"...
Эта операция просто сдвигает число (его двоичное представление) на n бит вправо.
Например 10101001>>1 равно 01010100 (последний бит стерся, грубо говоря, а первый стал нулем, хотя это и не всегда так бывает (посмотрите в интернете про арифметический и логический сдвиг))
0
1375 / 519 / 72
Регистрация: 21.07.2015
Сообщений: 1,304
29.08.2015, 20:04 11
Цитата Сообщение от Alexey104 Посмотреть сообщение
Буду разбираться с ">>="...
А тут нечего разбираться. В циклах перебираются все степени двойки от 2^31 до 2^0. И с помощью логического И (оператор &) проверяется значение конкретного разряда (бита).
0
4 / 4 / 0
Регистрация: 26.10.2014
Сообщений: 105
29.08.2015, 21:16  [ТС] 12
shmkv, Ещё раз спасибо, всё понятно.
0
4820 / 2286 / 287
Регистрация: 01.03.2013
Сообщений: 5,970
Записей в блоге: 29
29.08.2015, 23:37 13
Цитата Сообщение от Alexey104 Посмотреть сообщение
Буду разбираться с ">>="...
Это монадический оператор bind http://hackage.haskell.org/pac... 2--62--61- , разобраться с ним действительно полезно

А если серьезно - то на вопрос ТС нормального ответа так и не было. Но в этом разделе форума квалифицированные участники часто ленятся расписывать правильные ответы, могу предложить только мой недавний пример, приведенный для забаненного ныне участника - Объясните работу рекурсивной функции из книги Г. Шилдта
1
Неэпический
18108 / 10695 / 2062
Регистрация: 27.09.2012
Сообщений: 26,928
Записей в блоге: 1
29.08.2015, 23:48 14
Например 10101001>>1 равно 01010100
Неудачное число Вы подобрали для примера. Не факт, что результат будет таким.
The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a non-negative value, the value of the result is the integral part of the quotient of E1/2E2 . If E1 has a signed type and a negative value, the resulting value is implementation-defined.
0
4 / 4 / 0
Регистрация: 26.10.2014
Сообщений: 105
30.08.2015, 03:14  [ТС] 15
_Ivana, Спасибо, интересно было почитать.
Цитата Сообщение от _Ivana Посмотреть сообщение
А если серьезно - то на вопрос ТС нормального ответа так и не было.
В принципе, я уже сам дошёл до ответа в ходе экспериментов. Написал две функции для отображения первых x чисел ряда Фибоначчи - первую, с использованием рекурсии, вторую - с использованием цикла:

рекурсия:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
 
using namespace std;
 
unsigned long long int Fibonacci(unsigned long long int x)
{
  if (x==1 || x == 2)
     {
       return (x - 1);
     }
  else
     {
       return (Fibonacci(x - 2) + Fibonacci(x - 1));
     }   
}
 
int main()
{
  int counter = 1;
  int x;
  cout << "Set x:" << "\t";
  cin >> x;
  for ( ; counter <= x; counter++)
      {
        cout << counter << ":\t" << Fibonacci(counter) << "\n";
      }
  return 0;    
}
цикл:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
 
using namespace std;
 
int main()
{
  unsigned long long int x, n, y = 0, z = 1;
  int counter = 1;
  cout << "Set x:" << "\t";
  cin >> x;
  for ( ; counter <= x; counter++)
      {
        if (counter == 1)
           {
             cout << "1" << ":\t" << "0" << "\n";
           }
        else
           {
             if (counter == 2)
                {
                  cout << "2" << ":\t" << "1" << "\n";
                }
             else
                {
                  n = y + z;
                  cout << counter << ":\t" << n << "\n";
                  y = z;
                  z = n;
                }   
           }   
      }
  return 0;
}
Запустил первый вариант с х = 50 - пока машина вычисляла последние 5 чисел, успел навернуть 0,5 кофеёчка.) При этом загрузка ЦП доходила до 30-ти процентов.
Запустил второй вариант с х = 50 - результат был выведен на экран мгновенно после нажатия клавиши "Enter".
0
4820 / 2286 / 287
Регистрация: 01.03.2013
Сообщений: 5,970
Записей в блоге: 29
30.08.2015, 03:27 16
Цитата Сообщение от Alexey104 Посмотреть сообщение
дошёл до ответа
Подозреваю, что вы только начали свой путь
Хорошо, наивную экспоненциальную рекурсию вы пощупали. Теперь можно попробовать реализовать тех же Фибоначчей также рекурсивными функциями, но

1) экспоненциально с мемоизацией
2) линейно рекурсивно
3) линейно хвосторекурсивно - с аккумулятором.

И замерить время, сравнить с вариантом с циклом. Последний вариант можно даже посмотреть ассемблерный скомпилированный листинг и также сравнить с циклом.
0
4 / 4 / 0
Регистрация: 26.10.2014
Сообщений: 105
30.08.2015, 03:37  [ТС] 17
Цитата Сообщение от _Ivana Посмотреть сообщение
Подозреваю, что вы только начали свой путь
Это верно.
Цитата Сообщение от _Ivana Посмотреть сообщение
1) экспоненциально с мемоизацией
2) линейно рекурсивно
3) линейно хвосторекурсивно - с аккумулятором.
Звучит устрашающе. Я так понял, с выводами поспешил, ещё учиться и учиться...
0
4820 / 2286 / 287
Регистрация: 01.03.2013
Сообщений: 5,970
Записей в блоге: 29
30.08.2015, 03:40 18
Цитата Сообщение от Alexey104 Посмотреть сообщение
Я так понял, с выводами поспешил, ещё учиться и учиться...
А вот это правильный вывод Можете посмотреть мои рекомендации в теме по ссылке. А пока вам для развлечения ваша же экспоненциально рекурсивная функция но с мемоизацией - пункт 1 по моему списку.
C++
1
2
3
4
5
6
7
8
9
#include <iostream>
using namespace std;
 
typedef unsigned long long ull;
ull m[100];
 
ull fib(ull i) {m[i] = m[i] ? m[i] : (i<2 ? i : fib(i-1)+fib(i-2)); return m[i];}
 
int main() {cout<<fib(10)<<endl<<fib(50); return 0;}
Успешно time: 0 memory: 3456 signal:0
55
12586269025
https://ideone.com/rXhwi0
0
4 / 4 / 0
Регистрация: 26.10.2014
Сообщений: 105
30.08.2015, 19:44  [ТС] 19
Большое спасибо, завтра буду штудировать, а сейчас не мешало бы всхрапнуть, а то башка уже совсем ничего не соображает...

Добавлено через 15 часов 19 минут
_Ivana, Ваш алгоритм мне понятен, и, в отличие от моего, он считает молниеносно. Откуда такая разница в быстродействии, я так и не понял, но полагаю, для ответа на этот вопрос нужно изучить ваш список. Буду гуглить, если будут вопросы, задам здесь...

Добавлено через 28 минут
Цитата Сообщение от Alexey104 Посмотреть сообщение
Откуда такая разница в быстродействии, я так и не понял
Всё-таки дошло:
Цитата Сообщение от _Ivana Посмотреть сообщение
C++
1
m[i] = m[i] ? m[i]
Мы же не высчитываем при каждом рекурсивном вызове уже вычисленные при предыдущих вызовах значения! Яков умный...)
0
4820 / 2286 / 287
Регистрация: 01.03.2013
Сообщений: 5,970
Записей в блоге: 29
30.08.2015, 21:57 20
Лучший ответ Сообщение было отмечено Alexey104 как решение

Решение

Alexey104, правильно говоришь, Дядя Федор Называется мемоизация (или в простонародии динамическое программирование). Мощная технология, т.к. позволяет быстро считать и такие рекурсивные функции, которые в циклы развернуть не так тривиально, как Фибоначчей.

А дальше я бы все-таки посоветовал вам почитать SICP - хотя бы начало, про итеративные и рекурсивные алгоритмы.

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

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

Добавлено через 1 час 8 минут
ЗЫ ладно, вот ваш второй кот, переписанный через рекурсию:
C++
1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;
 
typedef unsigned long long int ull;
 
void loop(int i, int n, ull a, ull b) {cout<<i<<":\t"<<a<<"\n"; if (i<n) loop(i+1,n,b,a+b);}
 
int main() {int n; cin>>n; loop(0,50,0,1); return 0;}
Успешно time: 0 memory: 3464 signal:0
0: 0
1: 1
2: 1
3: 2
.............
48: 4807526976
49: 7778742049
50: 12586269025
ЗЫ и кстати, сравнение вы проводили некорректно - в первом случае вы каждый раз в цикле вызывали функцию, которая сначала все пересчитывала, а во втором - просто выводили промежуточные результаты вычислений.
1
30.08.2015, 21:57
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
30.08.2015, 21:57
Помогаю со студенческими работами здесь

С точки зрения закона
Поделитесь опытом, как лучше сделать. Регистрировался кто нибудь, как ООО или как нибуть ещё? И...

C точки зрения професcионала.
Существует сайт palazzo.su который я взялся и создать и продвинуть. На мой субъективный взгляд,...

Меню с точки зрения юзабилити
Какое левое(синее) меню вам кажется приятнее, читабельнее и смотрибельнее??)) вариант 1- так...

БП с точки зрения потребления энергии
Заинтересовал такой вопрос по поводу расхода электричества. Предположим, что есть некоторая...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru