Продолжаем проходить курс Computer Science CS50 и язык C. Изучаем рекурсию на примере гипотезы Коллатца — интересной (и простой для понимания) математической проблемы, которая все еще не решена учеными.
Гипотеза Коллатца:
- Возьмем любое положительное целое число
- Если оно чётное, то поделим его на 2
- Если нечётное, то умножаем на 3 и прибавляем 1 (получаем n * 3 + 1).
- Далее полученное число снова оцениваем — четное оно или нет и снова выполняем те же самые действия, до тех пор, пока не получим 1.
Итого, суть гипотезы Коллатца в том, что абсолютно любое число при применении к нему этих двух простых операций рано или поздно превратится в 1.
Если посмотреть на эту проблему в традиционном ключе и попробовать решить через цикл без применения рекурсии, получим такой код (решение Игроглаза):
#include <stdio.h>
int collatz(int);
int main (void)
{
int n;
printf("Which number to Collatzinate?\n");
scanf("%d", &n);
printf("Number of steps for %d is %d", n, collatz(n));
return 0;
}
int collatz(int n)
{
int steps = 0;
while (n != 1)
{
if (n % 2 == 0 && n > 1)
n /= 2;
else
n = (n * 3) + 1;
steps++;
}
return steps++;
}
Теперь попробуем написать код с рекурсией. У нас один базовый случай: если n равно 1, то пора выйти из рекурсии. И у нас есть два рекурсивных случая:
- если n четное, мы делаем один рекурсивный случай, деля n на 2.
- если n нечетное, мы делаем другой рекурсивный случай для 3 раз n плюс 1.
Итак, цель — написать рекурсивную функцию Коллатца, где мы передаем значение n, и функция вычисляет, сколько шагов потребуется, чтобы добраться до 1. Если n равно 1, то требуется 0 шагов. В противном случае будем делать один шаг плюс столько же шагов, сколько потребуется либо для n, деленного на 2, если n четное, либо для 3*n плюс 1, если n нечетное.
Вот несколько тестовых значений, чтобы проверить различные числа Коллатца, а также увидеть шаги, которые необходимо пройти, чтобы прийти к 1.
| n | collatz(n) | Шаги |
| 1 | 0 | 1 |
| 2 | 1 | 2, 1 |
| 3 | 7 | 3, 5, 8, 4, 2, 1 |
| 4 | 2 | 4, 2, 1 |
| 5 | 5 | 5, 8, 4, 2, 1 |
| 6 | 8 | 6, 3, 5, 8, 4, 2, 1 |
| 7 | 16 | 7, 11, 17, 26, 13, 20, 10, 5, 8, 4, 2, 1 |
| 8 | 3 | 8, 4, 2, 1 |
Итак, попробуем рекурсивно определить число Коллатца для n; чтобы наши функция вычисляла, сколько шагов требуется для того, чтобы подойти к 1.
Рекурсия — вариант Штукенции (как посчитать количество шагов для n, если к примеру n=27; можно подставить свое любое значение в скобках):
#include <cs50.h>
#include <stdio.h>
int collatz (int n);
int steps = 0;
int main (void)
{
int steps_number = collatz(27);
printf ("%i\n", steps_number);
}
int collatz (int n)
{
if (n == 1)
return steps;
else if (n % 2 == 0)
{
steps += 1;
return collatz (n / 2);
}
else if (n % 2 != 0)
{
steps += 1;
return collatz(3 * n + 1);
}
return 0;
}
Штукенция отлично справилась 🙂 От меня она услышала подсказку насчет глобальной переменной. Я тоже от нее кое-чему научился. Изначально я написал корявую версию рекурсии, которая корректно не выходила из вложенных функций — ошибочная рекурсия от Игроглаза:
#include <stdio.h>
int collatz(int);
int steps = 0;
int main (void)
{
int n;
printf("Which number to Collatzinate?\n");
scanf("%d", &n);
printf("Number of steps for %d is", n);
collatz(n);
printf(" %d", steps);
return 0;
}
int collatz(int n)
{
if (n == 1)
return 0;
if (n % 2 == 0 && n > 0)
{
n = n / 2;
steps++;
collatz(n);
}
else
{
n = (n * 3) + 1;
steps++;
collatz(n);
}
}
При этом этот код выдает правильный результат. После решения задачи мы со Штукенцией поменялись стульями — чтобы она посмотрела мой код, а я ее. Краем глаза я увидел, что она выходит из рекурсивных случаев через return и удивился. Но еще до обмена стульями, я удивился, что у меня у меня очень странный возврат steps if (n == 1) return steps; — выдает какие-то пятизначные значения, хотя по дебагеру количество шагов — корректно. Поэтому я и поменял steps на 0 и вывел чисто steps в main в первой версии программы 😉
Начал дебажить и в процессе понял, что из рекурсивных функция я не выхожу, а оставляю их висеть… Вспомнил про return у Штукенции (эх, жаль я его подсмотрел случайно, а не сам додумался). В итоге, вот корректная версия рекурсии от Игроглаза:
#include <stdio.h>
int collatz(int);
int steps = 0;
int main (void)
{
int n;
printf("Which number to Collatzinate?\n");
scanf("%d", &n);
printf("Number of steps for %d is %d", n, collatz(n));
return 0;
}
int collatz(int n)
{
if (n == 1)
return steps;
if (n % 2 == 0 && n > 0)
{
n = n / 2;
steps++;
return collatz(n);
}
else
{
n = (n * 3) + 1;
steps++;
return collatz(n);
}
}
Вот уж не думал, что гуманитарии справятся с такими вещами 🙂 Тем не менее, мы со Штукенцией два что ни на есть гуманитария. И все получается. Так что и у вас все будет отлично!
