11.10 Рекурсия и работа с кучей (повторение)

Указатели

Передача указателя в функцию

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

Передача функции в виде указателя

Работа с кучей

Бывает по-разному, но обычно у запущенной программы в ОС:

Почему так? Так пошла эволюцияПотому что!!! (+потому что виртуальная память, а так удобнее)

Вид памяти

Тип переменных

Где размещается

Время жизни

Объём

Можно ли из функции возвращать указатель

Статическая

Глобальные, статические

В коде программы (секция data)

Всё время работы программы

Ограничен здравым смыслом (если проинициализировать, лежит прямо в файле с программой)

Да

Локальная

Локальные

В секции stack, доступной после запуска

От входа в функцию до выхода из неё

Ограничен размером стека (зависит от архитектуры; может быть небольшим)

Нет

Динамическая

таких нет, только указатели

В куче, доступной после запуска

От момента явного выделения памяти до момента явного освобождения

Актуально неограничен

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

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

Malloc, Calloc и free

Realloc

Getline и Getdelim

Работа getline

  1. {OK} Ввод строки произвольного размера (для этого надо инициализировать приёмник NULL-ом)

(требуется free()):

Рекурсия

С точки зрения Си рекурсия — это «всего лишь» когда функция {в конце концов} вызывает саму себя.

Уместность рекурсии

Объём входных данных ⩽ log вложенности рекурсивных вызовов

ЧаВо

Q: Какие задачи проще решать рекурсивно?

  1. Заданные рекуррентными соотношениями (например, числа Фибоначчи, факториал, вообще всякое

    • Следите за уместностью рекурсии (объём входных данных ⩽ log вложенности рекурсивных вызовов)
    • <!> Следите за паразитными вычислениями (например, рекурсивные числа Фибоначчи — это ужасно)

      • {i} Написать рекурсивную функцию для n-го числа Фибоначчи, вычислить 50-е (40-е? 30-е?)

  2. Задачу можно свести к рекуррентному соотношению

  3. В решении можно выделить одну или несколько подзадач, которые решаются тем же алгоритмом, но работают с меньшим числом входных данных

    • Объём из N обрабатываемых данных при рекурсивном вызове сокращается не на константное число элементов, а в k раз (путь даже k близко к 1), или (как в случае задачи с ферзями) — на число, кратное $$\sqrt N$$

    • У рекурсивной функции оказывается непустой значимый контекст (переменные, которые меняются от вызова к вызову, по после возврата — восстанавливают значение)
      • Особенно когда рекурсивных вызовов более одного.
    • Пример: задача о ферзях. На иллюстрации — третий шаг классического алгоритма
      • queens.png

      • Сведение к меньшей подзадаче (в упрощённом виде): поставить ферзя на доску N×N — это цикл по всем возможным позициям ферзя в N-й вертикали + проверка, можно ли поставить ферзя на доску N-1×N
      • Зелёным цветом отмечена область, в которой могут произойти рекурсивные вызовы (не находящиеся под боем пола) + розовым — находящиеся подбоем только по диагонали. Даже если забыть про диагонали (посчитать все цветные квадраты), на k-м шаге из рассмотрения исключается более, чем k*2-1 поле.
        • ⇒ в действительности задача N×N сводится к N проверок и задаче N-1×N-1 — и это свидетелсьтво уместной рекурсии

TODO пример задачи, которая сводится к простому рекуррентному соотношению (желательно, с достаточно простым рекурсивным решением)

Если успеем — ещё что-нибудь поразбираем

Д/З (можно делать)

Напоминаю, что в заданиях типа «написать функцию» сдавать надо именно текст функции, к которому будет приписана тестирующая её программа. Функции main() в этих решениях нет: его допишет EJudge. Если это не оговорено отдельно, допускается также необходимые #include, объявление глобальных переменных и второстепенных функций.

  1. EJudge: ManyGetlines 'Много строк'

    Написать программу, вводящую неизвестное количество строк getline()-ом. Массив указателей (aka «массив строк») регулярно обновлять с помощью realloc(). Вывести (с помощью strstr()) все строки, которые содержат последнюю. Конец ввода — EOF, переводы строки исключаются из поиска. Не забыть вызвать free() для всех заказанных на куче фрагментов памяти.

    Input:

        October 2026       November 2026      December 2026
    Su     4 11 18 25      1  8 15 22 29         6 13 20 27
    Mo     5 12 19 26      2  9 16 23 30         7 14 21 28
    Tu     6 13 20 27      3 10 17 24         1  8 15 22 29
    We     7 14 21 28      4 11 18 25         2  9 16 23 30
    Th  1  8 15 22 29      5 12 19 26         3 10 17 24 31
    Fr  2  9 16 23 30      6 13 20 27         4 11 18 25
    Sa  3 10 17 24 31      7 14 21 28         5 12 19 26
    7 14
    Output:

    Mo     5 12 19 26      2  9 16 23 30         7 14 21 28
    We     7 14 21 28      4 11 18 25         2  9 16 23 30
    Sa  3 10 17 24 31      7 14 21 28         5 12 19 26
  2. Вы будете смеяться, но хороший пример рекуррентного соотношения с уместной рекурсивной реализацией… — числа Фибоначчи!
    • EJudge: FibPair 'Числа фибоначчи-2'

      Рекурсивный алгоритм вычисления последовательной пары xчисел Фибоначчи

      • $$ F(2k) = F(k)(2F(k+1) - F(k)) $$ (если нужно чётное по номеру число Фибоначчи)

      • $$ F(2k+1) = F(k)^2 + F(k+1)^2 $$ (если нужно нечётное по номеру число)

      Написать рекурсивную функцию fib(int n, int *f, int *f1), которая записывает в f и f1, соответственно, n-е и n+1-е числа Фибоначчи (нумерация начинается с 0: 0, 1, 1, 2, 3, 5, 8, 13, …).

      • Пример тестирующей программы:
           1 #include <stdio.h>
           2 int main(void) {
           3         int f, f1;
           4         int n;
           5         while (scanf("%d", &n)>0) {
           6                 fib(n, &f, &f1);
           7                 printf("%d: %d\n", n, f);
           8         }
           9         return 0;
          10 }
        
      Input:

      0 1 6 7 8 10 30
      Output:

      0: 0
      1: 1
      6: 8
      7: 13
      8: 21
      10: 55
      30: 832040
  3. EJudge: SudanFun 'Быстро растущая функция'

    Написать какую угодно (добрый совет: рекурсивную) функцию, unsigned long sudan(int n, unsigned long int x, unsigned long y), вычисляющую следующее рекуррентное соотношение

    • $$ F_0 (x, y) = x+y $$

    • $$ F_{n+1} (x, 0) = x$$, если $$ n⩾0 $$

    • $$ F_{n+1} (x, y+1) = F_n (F_{n+1} (x, y), F_{n+1} (x, y) + y + 1)$$, если $$ n⩾0 $$

    Функция растёт очень быстро, так что в тестах n, x, и y будут достаточно малы, чтобы не вызывать переполнения unsigned long и исчерпания стека вызовов. Тестовая программа может выглядеть так:

    • int main(void) {
              int n, x, y;
              while(scanf("%d%d%d", &n, &x, &y)>0)
                      printf("%lu\n", sudan(n, x, y));
              return 0;
      }
    Input:

    0 0 0
    1 4 1
    1 2 4
    2 1 2
    1 1 10
    2 2 2
    Output:

    0
    9
    58
    10228
    3060
    15569256417
  4. EJudge: RevBraces 'Скобки назад'

    Рассмотрим правильную скобочную последовательность, в которой любая пара () скобок может содержать внутри себя буквы — например (qwe) или (qwe)()((rty)(uio)). Применим к ней следующий рекурсивный алгоритм:

    1. Если строка — это скобочная последовательность, содержащая единственную пару скобок (в начале и конце), просто выведем всю строку со скобками

    2. Если строка содержит несколько скобочных последовательностей, применим наш алгоритм к каждой из них в порядке от последней до начальной, не забывая выводить их скобки

    Написать итеративный алгоритм решения этой задачи — функцию backwards(char *str), которая выводит скобочную последовательность с буквами, преобразованную в соответствии с правилами. Тестирующая программа может выглядеть так:

    •    1 #include <stdio.h>
         2 #include <stdlib.h>
         3 int main(void) {
         4         char *s;
         5         size_t n, l;
         6         l = getline(&s, &n, stdin);
         7         s[l-1] = 0;
         8         backwards(s);
         9         free(s);
        10         return 0;
        11 } 
      
    Input:

    (qwe)(()((rty)(uio)))
    Output:

    (((uio)(rty))())(qwe)

LecturesCMC/AL/Prac/20_MallocRecursion (последним исправлял пользователь FrBrGeorge 2026-11-14 14:02:15)