Структурное программирование

Информатика 11 класс · тест: 5 вопросов

Структурное программирование — это не просто набор правил, а философия, которая превращает хаос кода в организованную архитектуру, понятную не только компьютеру, но и другим людям.

От хаоса к структуре: превращение разрозненного кода в организованную систему

Введение: Когда код становится архитектурой

Представь, что тебе нужно построить небоскрёб. Можно ли просто начать складывать кирпичи один на другой, надеясь, что всё как-нибудь само сложится? Конечно, нет. Нужен чертёж, фундамент, каркас, система коммуникаций. То же самое и с программами: когда они разрастаются до тысяч строк кода, без продуманной архитектуры превращаются в хаос.

💡 Определение

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

Исторический контекст: Революция Дейкстры

В начале 1960-х программирование было похоже на дикий запад. Программисты использовали команду goto, что превращало код в «спагетти»: следить за логикой было почти невозможно.

От кодовых спагетти к структурированным алгоритмам

От «кодовых спагетти» к структурированным алгоритмам: революция читаемости

📜 Прорыв 1968 года

Нидерландский учёный Эдсгер Дейкстра опубликовал знаменитую статью «Go To Statement Considered Harmful».

Его идея: любую программу можно построить из трёх базовых конструкций — последовательность, ветвление, цикл — без использования goto.

🎯 Почему это важно для тебя?

  • Современные языки (Python, JavaScript, C) построены на этих принципах
  • Без структурного подхода невозможно работать в команде
  • Язык C стал одним из первых массовых языков, реализовавших идеи структурного программирования

Пять принципов структурного программирования

Эти принципы — фундамент современной разработки ПО. Понимание их делает тебя не просто кодером, а инженером.

Принцип 1

Три базовые конструкции

Любую задачу можно решить, комбинируя:

  • Последовательность — шаги один за другим
  • Ветвление — выбор пути: if (...) {...} else {...}
  • Цикл — повторение: while (...), for (...)

Почему три? Это минимальный набор, достаточный для выражения любой вычислимой функции (доказано математически!).

Принцип 2

Вложенность конструкций

Можно вкладывать циклы в условия, условия в циклы произвольным образом.

Аналогия: Как в матрёшке — одна кукла внутри другой, но каждая сама по себе целостный объект.

Принцип 3

Функции

Если фрагмент кода повторяется или логически целостен, выноси его в отдельную функцию.

Пример из жизни: Ты не описываешь каждый раз, как открыть дверь, а говоришь: «Открой дверь». Это — функция.

Принцип 4

Один вход, один выход

Каждый блок (функция, цикл, условие) должен иметь чётко определённое начало и конец.

Это упрощает отладку: ты точно знаешь, где что начинается и заканчивается.

Принцип 5

Метод «сверху вниз»

Сначала пиши общую структуру программы, используя «заглушки» (пустые функции). Затем постепенно заполняй детали.

Аналогия: Сначала пишешь план эссе, потом раскрываешь каждый пункт.

Метод сверху вниз: от общего замысла к деталям

Метод «сверху вниз»: от общего замысла к деталям, сохраняя видимость целого

Функции: кирпичики сложности

Примеры ниже — на Си, Паскале и Python: у каждого есть вкладки, выбери свой язык.

Функция — это логически целостный фрагмент кода, который решает подзадачу. Вместо копирования формулы три раза, создаём функцию.

✨ Преимущества функций

  • Переиспользование: Написал один раз — используешь многократно
  • Читаемость: Вместо формулы — понятное имя функции
  • Отладка: Ошибка ищется в одном месте

📋 Синтаксис функции

тип_результата имя_функции(параметры) {
    // тело функции
    return результат;
}
function имя_функции(параметры): тип_результата;
begin
  { тело функции }
  имя_функции := результат;
end;
def имя_функции(параметры):
    # тело функции
    return результат

💡 Пример: Вычисление длины отрезка

Формула из геометрии для вычисления длины отрезка по координатам концов:

#include <math.h>

double dlina_otrezka(double x1, double y1, double x2, double y2) {
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
function dlina_otrezka(x1, y1, x2, y2: real): real;
begin
  dlina_otrezka := sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
end;
import math

def dlina_otrezka(x1, y1, x2, y2):
    return math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))
Функции как модульные блоки программы

Функции — модульные блоки, которые легко комбинировать благодаря чётким интерфейсам

Прототипы функций и порядок объявления

Функцию нужно объявить до её использования. В Си, если функция определена после main(), пишут прототип; в Паскале для того же служит слово forward; в Python функция должна быть описана раньше, чем выполнится её вызов.

🔍 Структура программы с прототипом

#include <stdio.h>
#include <math.h>

// Прототип функции (объявление)
double dlina_otrezka(double x1, double y1, double x2, double y2);

int main() {
    double d = dlina_otrezka(0, 0, 3, 4);
    printf("Длина: %.2f\n", d);  // Выведет: 5.00
    return 0;
}

// Определение функции (реализация)
double dlina_otrezka(double x1, double y1, double x2, double y2) {
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
program Segment;

{ предварительное объявление — аналог прототипа в Си }
function dlina_otrezka(x1, y1, x2, y2: real): real; forward;

procedure run;
var
  d: real;
begin
  d := dlina_otrezka(0, 0, 3, 4);
  writeln('Длина: ', d:0:2);   { выведет: 5.00 }
end;

{ определение функции }
function dlina_otrezka(x1, y1, x2, y2: real): real;
begin
  dlina_otrezka := sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
end;

begin
  run;
end.
import math

# в Python функция должна быть описана до того, как её вызов выполнится
def dlina_otrezka(x1, y1, x2, y2):
    return math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))

d = dlina_otrezka(0, 0, 3, 4)
print(f"Длина: {d:.2f}")   # выведет: 5.00

Параметры: передача по значению и по ссылке

Обычно параметры передаются по значению: подпрограмма получает копию. Чтобы подпрограмма могла изменить саму переменную, в Си передают её адрес — указатель, в Паскале описывают параметр словом var. В Python так изменить целое число нельзя: функция возвращает новое значение, и его присваивают.

📋 Передача по значению

Изменяется только локальная копия:

void increment(int x) {
    x = x + 1;  // изменяется копия
}

int main() {
    int a = 5;
    increment(a);
    printf("%d\n", a);  // Выведет: 5
    return 0;
}
procedure increment(x: integer);
begin
  x := x + 1;   { изменяется копия }
end;

var
  a: integer;
begin
  a := 5;
  increment(a);
  writeln(a);   { выведет: 5 }
end.
def increment(x):
    x = x + 1   # изменяется локальная переменная

a = 5
increment(a)
print(a)   # выведет: 5

📍 Передача по ссылке (указатель в Си, var в Паскале)

Изменяется оригинальная переменная:

void increment(int *x) {
    *x = *x + 1;  // изменяется оригинал
}

int main() {
    int a = 5;
    increment(&a);  // передаём адрес
    printf("%d\n", a);  // Выведет: 6
    return 0;
}
{ в Паскале вместо указателя — параметр-переменная var }
procedure increment(var x: integer);
begin
  x := x + 1;   { изменяется оригинал }
end;

var
  a: integer;
begin
  a := 5;
  increment(a);   { передаём саму переменную }
  writeln(a);     { выведет: 6 }
end.
# в Python целое число внутри функции не изменить —
# функция возвращает новое значение, а его присваивают
def increment(x):
    return x + 1

a = 5
a = increment(a)
print(a)   # выведет: 6

💡 Аналогия

Передача по значению — это как дать другу ксерокопию документа. Он может делать с ней что угодно, оригинал не изменится.

Передача по указателю — дать адрес сейфа с оригиналом. Теперь друг может изменить сам документ.

Рекурсия: Когда функция вызывает саму себя

Рекурсия — это метод решения задач, при котором функция вызывает саму себя с изменёнными параметрами.

Рекурсия как матрёшка: упрощение до базового случая

Рекурсия: каждый шаг упрощает задачу до базового случая, затем результаты собираются обратно

📌 Классический пример: Факториал

Математическое определение: n! = 1 × 2 × 3 × ... × n

Рекурсивное определение:

  • F(n) = 1, если n ≤ 1
  • F(n) = n × F(n-1), если n > 1

Код:

#include <stdio.h>

int factorial(int n) {
    if (n <= 1) {
        return 1;  // базовый случай
    } else {
        return n * factorial(n - 1);  // рекурсивный вызов
    }
}

int main() {
    printf("5! = %d\n", factorial(5));  // Выведет: 120
    return 0;
}
program Factorial;

function factorial(n: integer): integer;
begin
  if n <= 1 then
    factorial := 1                          { базовый случай }
  else
    factorial := n * factorial(n - 1);      { рекурсивный вызов }
end;

begin
  writeln('5! = ', factorial(5));   { выведет: 120 }
end.
def factorial(n):
    if n <= 1:
        return 1                       # базовый случай
    else:
        return n * factorial(n - 1)    # рекурсивный вызов

print("5! =", factorial(5))   # выведет: 120

⚠️ Критически важно: Граничное условие

Без условия остановки рекурсия будет вызывать себя бесконечно (переполнение стека!).

Граничное условие — это момент, когда функция перестаёт обращаться к себе и возвращает результат напрямую.

Практический пример: Сумма цифр числа

Вычислим сумму цифр натурального числа с помощью рекурсии.

💭 Рекурсивная логика

  • Если n < 10, то сумма цифр равна самому числу (базовый случай)
  • Иначе: сумма цифр n = последняя цифра (n % 10) + сумма цифр оставшейся части (n / 10)

Код:

#include <stdio.h>

int summa_cifr(int n) {
    if (n < 10) {
        return n;  // базовый случай
    } else {
        return (n % 10) + summa_cifr(n / 10);  // рекурсия
    }
}

int main() {
    printf("Сумма цифр 12345: %d\n", summa_cifr(12345));
    // Выведет: 15
    return 0;
}
program DigitSum;

function summa_cifr(n: integer): integer;
begin
  if n < 10 then
    summa_cifr := n                                     { базовый случай }
  else
    summa_cifr := (n mod 10) + summa_cifr(n div 10);    { рекурсия }
end;

begin
  writeln('Сумма цифр 12345: ', summa_cifr(12345));   { выведет: 15 }
end.
def summa_cifr(n):
    if n < 10:
        return n                                  # базовый случай
    else:
        return (n % 10) + summa_cifr(n // 10)     # рекурсия

print("Сумма цифр 12345:", summa_cifr(12345))   # выведет: 15

🔄 Рекурсия

  • Элегантна для задач с естественной вложенностью
  • Идеальна для деревьев, обхода графов
  • Использует стек вызовов

➿ Итерация

  • Эффективнее по памяти
  • Не создаёт множество вызовов в стеке
  • Подходит для простых циклических задач

📊 Факториал итеративно (для сравнения)

int factorial_iter(int n) {
    int result = 1;
    for (int i = 1; i <= n; i++) {
        result *= i;
    }
    return result;
}
function factorial_iter(n: integer): integer;
var
  i, f: integer;
begin
  f := 1;
  for i := 1 to n do
    f := f * i;
  factorial_iter := f;
end;
def factorial_iter(n):
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

Практика: периметр треугольника

Применим метод «сверху вниз» для решения реальной задачи.

📝 Задача

Дано три точки A, B, C с координатами. Найти периметр треугольника ABC.

🔷 Шаг 1: Общая структура с заглушкой

#include <stdio.h>
#include <math.h>

// Прототип функции
double dlina_otrezka(double x1, double y1, double x2, double y2);

int main() {
    double xa, ya, xb, yb, xc, yc, p;
    
    // Ввод координат
    printf("Введите координаты точки A (xa ya): ");
    scanf("%lf %lf", &xa, &ya);
    
    printf("Введите координаты точки B (xb yb): ");
    scanf("%lf %lf", &xb, &yb);
    
    printf("Введите координаты точки C (xc yc): ");
    scanf("%lf %lf", &xc, &yc);
    
    // Вычисление периметра
    p = dlina_otrezka(xa, ya, xb, yb) +
        dlina_otrezka(xb, yb, xc, yc) +
        dlina_otrezka(xc, yc, xa, ya);
    
    printf("Периметр треугольника: %.2f\n", p);
    
    return 0;
}

// Заглушка (пока возвращает 0)
double dlina_otrezka(double x1, double y1, double x2, double y2) {
    return 0;
}
program Perimeter;
var
  xa, ya, xb, yb, xc, yc, p: real;

{ заглушка (пока возвращает 0) }
function dlina_otrezka(x1, y1, x2, y2: real): real;
begin
  dlina_otrezka := 0;
end;

begin
  { ввод координат }
  write('Введите координаты точки A (xa ya): ');
  readln(xa, ya);
  write('Введите координаты точки B (xb yb): ');
  readln(xb, yb);
  write('Введите координаты точки C (xc yc): ');
  readln(xc, yc);

  { вычисление периметра }
  p := dlina_otrezka(xa, ya, xb, yb) +
       dlina_otrezka(xb, yb, xc, yc) +
       dlina_otrezka(xc, yc, xa, ya);

  writeln('Периметр треугольника: ', p:0:2);
end.
# заглушка (пока возвращает 0)
def dlina_otrezka(x1, y1, x2, y2):
    return 0

# ввод координат
xa, ya = map(float, input("Введите координаты точки A (xa ya): ").split())
xb, yb = map(float, input("Введите координаты точки B (xb yb): ").split())
xc, yc = map(float, input("Введите координаты точки C (xc yc): ").split())

# вычисление периметра
p = (dlina_otrezka(xa, ya, xb, yb) +
     dlina_otrezka(xb, yb, xc, yc) +
     dlina_otrezka(xc, yc, xa, ya))

print(f"Периметр треугольника: {p:.2f}")

Преимущество подхода: Сначала проверили логику программы (правильно ли вызываются функции?), затем заполнили детали.

🔷 Шаг 2: Реализация функции

double dlina_otrezka(double x1, double y1, double x2, double y2) {
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
function dlina_otrezka(x1, y1, x2, y2: real): real;
begin
  dlina_otrezka := sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
end;
def dlina_otrezka(x1, y1, x2, y2):
    return math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))

Обобщение: n-угольник

Как модифицировать программу для n-угольника? Храним координаты в массивах и используем цикл.

🔺 Программа для n-угольника

#include <stdio.h>
#include <math.h>

double dlina_otrezka(double x1, double y1, double x2, double y2);

int main() {
    int n;
    printf("Введите количество вершин: ");
    scanf("%d", &n);
    
    double x[n], y[n];
    
    // Ввод координат
    for (int i = 0; i < n; i++) {
        printf("Координаты вершины %d (x y): ", i + 1);
        scanf("%lf %lf", &x[i], &y[i]);
    }
    
    // Вычисление периметра
    double p = 0;
    for (int i = 0; i < n; i++) {
        int next = (i + 1) % n;  // следующая вершина
        p += dlina_otrezka(x[i], y[i], x[next], y[next]);
    }
    
    printf("Периметр %d-угольника: %.2f\n", n, p);
    
    return 0;
}

double dlina_otrezka(double x1, double y1, double x2, double y2) {
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
program Polygon;
const
  MAXN = 100;
var
  x, y: array[1..MAXN] of real;
  n, i, next: integer;
  p: real;

function dlina_otrezka(x1, y1, x2, y2: real): real;
begin
  dlina_otrezka := sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
end;

begin
  write('Введите количество вершин: ');
  readln(n);

  { ввод координат }
  for i := 1 to n do
  begin
    write('Координаты вершины ', i, ' (x y): ');
    readln(x[i], y[i]);
  end;

  { вычисление периметра }
  p := 0;
  for i := 1 to n do
  begin
    next := i mod n + 1;   { следующая вершина; после n — снова 1 }
    p := p + dlina_otrezka(x[i], y[i], x[next], y[next]);
  end;

  writeln('Периметр ', n, '-угольника: ', p:0:2);
end.
import math

def dlina_otrezka(x1, y1, x2, y2):
    return math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))

n = int(input("Введите количество вершин: "))
x = [0.0] * n
y = [0.0] * n

# ввод координат
for i in range(n):
    x[i], y[i] = map(float, input(f"Координаты вершины {i + 1} (x y): ").split())

# вычисление периметра
p = 0
for i in range(n):
    nxt = (i + 1) % n   # следующая вершина
    p += dlina_otrezka(x[i], y[i], x[nxt], y[nxt])

print(f"Периметр {n}-угольника: {p:.2f}")

Почему (i + 1) % n? Чтобы замкнуть контур: после последней вершины (i = n-1) идёт первая (индекс 0).

Продвинутая рекурсия: Динамическое программирование

Рассмотрим задачу подсчёта количества программ для исполнителя Плюс.

🎯 Постановка задачи

Исполнитель Плюс имеет три команды:

  • +1 (прибавить 1)
  • +2 (прибавить 2)
  • +4 (прибавить 4)

Вопрос: Сколько существует программ, превращающих число 20 в число 30?

Дерево рекурсивных вызовов

Рекурсивное дерево вычислений: каждый путь от листа к корню — возможная программа

🧮 Рекурсивное решение

Обозначим количество программ для получения числа n как K(n).

Базовый случай:

  • K(n) = 0 при n < 20 (невозможно получить)
  • K(20) = 1 (пустая программа)

Рекурсивный шаг:

Число n можно получить из n-1, n-2 или n-4:

K(n) = K(n-1) + K(n-2) + K(n-4) при n > 20

❌ Наивная рекурсия (неэффективная)

#include <stdio.h>

int K(int n) {
    if (n < 20) {
        return 0;
    } else if (n == 20) {
        return 1;
    } else {
        return K(n-1) + K(n-2) + K(n-4);
    }
}

int main() {
    printf("K(30) = %d\n", K(30));
    return 0;
}
program KRecursive;

function K(n: integer): integer;
begin
  if n < 20 then
    K := 0
  else if n = 20 then
    K := 1
  else
    K := K(n - 1) + K(n - 2) + K(n - 4);
end;

begin
  writeln('K(30) = ', K(30));
end.
def K(n):
    if n < 20:
        return 0
    elif n == 20:
        return 1
    else:
        return K(n - 1) + K(n - 2) + K(n - 4)

print("K(30) =", K(30))

Проблема: Функция многократно пересчитывает одни и те же значения!

✅ Итеративный подход (оптимизация)

#include <stdio.h>

int main() {
    int K[31];
    
    // Базовые случаи
    for (int i = 0; i < 20; i++) {
        K[i] = 0;
    }
    K[20] = 1;
    
    // Заполнение таблицы
    for (int i = 21; i <= 30; i++) {
        K[i] = K[i-1] + K[i-2] + K[i-4];
    }
    
    printf("Ответ: K(30) = %d\n", K[30]);
    return 0;
}
program KTable;
var
  K: array[0..30] of integer;   { индексы от 0 — как в формуле }
  i: integer;
begin
  { базовые случаи }
  for i := 0 to 19 do
    K[i] := 0;
  K[20] := 1;

  { заполнение таблицы }
  for i := 21 to 30 do
    K[i] := K[i - 1] + K[i - 2] + K[i - 4];

  writeln('Ответ: K(30) = ', K[30]);
end.
K = [0] * 31

# базовые случаи: K[0..19] = 0 уже есть
K[20] = 1

# заполнение таблицы
for i in range(21, 31):
    K[i] = K[i - 1] + K[i - 2] + K[i - 4]

print("Ответ: K(30) =", K[30])

Преимущество: Каждое значение вычисляется ровно один раз. Сложность O(n)!

📊 Результаты вычислений

n 20 21 22 23 24 25 26 27 28 29 30
K(n) 1 1 2 3 6 10 18 31 55 96 169

Ответ: 169 различных программ

Рекурсия в природе и культуре

Рекурсивные структуры окружают нас повсюду!

Фракталы — геометрическое воплощение рекурсии

Фракталы — геометрическое воплощение рекурсии: каждая часть повторяет структуру целого

🌿 Природные примеры

  • Фракталы в растениях: Лист папоротника — каждая ветвь повторяет форму всего листа
  • ДНК: Процесс репликации — молекула копирует саму себя
  • Снежинки: Кристаллическая структура с повторяющимися паттернами

🎨 Культурные примеры

  • «Дом, который построил Джек»: Каждая строфа включает предыдущую + новый элемент
  • Эффект Дросте: Изображение, содержащее само себя (упаковка какао)
  • Мемы: Мем про мем про мем...

🔺 Треугольник Серпинского

Алгоритм построения:

  1. Возьми равносторонний треугольник
  2. Раздели его на 4 маленьких (средними линиями)
  3. Удали центральный
  4. Повтори шаги 2-3 для каждого оставшегося треугольника

Базовый случай: Когда размер треугольника < 1 пиксель — прекрати.

Работа с массивами и указателями

В Си массивы передаются по указателю (даже если синтаксически это выглядит как передача массива), поэтому функция может изменить элементы. В Паскале для этого массив передают как var-параметр (или const, если менять не нужно), в Python функция получает сам список и тоже может менять его элементы.

📊 Пример: Функция нахождения максимума в массиве

#include <stdio.h>

int find_max(int arr[], int size) {
    int max = arr[0];
    for (int i = 1; i < size; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }
    return max;
}

int main() {
    int numbers[] = {3, 7, 2, 9, 1};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    
    printf("Максимум: %d\n", find_max(numbers, size));
    // Выведет: 9
    
    return 0;
}
program FindMax;
type
  TArr = array[1..5] of integer;
var
  numbers: TArr;

function find_max(const arr: TArr; size: integer): integer;
var
  i, max: integer;
begin
  max := arr[1];
  for i := 2 to size do
    if arr[i] > max then
      max := arr[i];
  find_max := max;
end;

begin
  numbers[1] := 3; numbers[2] := 7; numbers[3] := 2;
  numbers[4] := 9; numbers[5] := 1;
  writeln('Максимум: ', find_max(numbers, 5));   { выведет: 9 }
end.
def find_max(arr):
    biggest = arr[0]
    for i in range(1, len(arr)):   # размер список знает сам
        if arr[i] > biggest:
            biggest = arr[i]
    return biggest

numbers = [3, 7, 2, 9, 1]
print("Максимум:", find_max(numbers))   # выведет: 9

Важно: в Си размер массива нужно передавать отдельным параметром — функция его не знает. В Python размер списка даёт len().

🔄 Пример: Функция обмена значений

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 5, y = 10;
    printf("До обмена: x = %d, y = %d\n", x, y);
    
    swap(&x, &y);
    
    printf("После обмена: x = %d, y = %d\n", x, y);
    // Выведет: x = 10, y = 5
    
    return 0;
}
program SwapDemo;
var
  x, y: integer;

procedure swap(var a, b: integer);   { var — изменяем сами переменные }
var
  temp: integer;
begin
  temp := a;
  a := b;
  b := temp;
end;

begin
  x := 5;
  y := 10;
  writeln('До обмена: x = ', x, ', y = ', y);
  swap(x, y);
  writeln('После обмена: x = ', x, ', y = ', y);
  { выведет: x = 10, y = 5 }
end.
x, y = 5, 10
print(f"До обмена: x = {x}, y = {y}")

# в Python функция не может поменять переменные вызывающего,
# зато обмен делается одной строкой
x, y = y, x

print(f"После обмена: x = {x}, y = {y}")
# выведет: x = 10, y = 5

🔁 Рекурсивный поиск максимума

#include <stdio.h>

int max_recursive(int arr[], int n) {
    // Базовый случай: один элемент
    if (n == 1) {
        return arr[0];
    }
    
    // Рекурсивный случай
    int max_rest = max_recursive(arr, n - 1);
    return (arr[n - 1] > max_rest) ? arr[n - 1] : max_rest;
}

int main() {
    int numbers[] = {3, 7, 2, 9, 1, 5};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    
    printf("Максимум: %d\n", max_recursive(numbers, size));
    
    return 0;
}
program MaxRecursive;
type
  TArr = array[1..6] of integer;
var
  numbers: TArr;

function max_recursive(const arr: TArr; n: integer): integer;
var
  max_rest: integer;
begin
  if n = 1 then
    max_recursive := arr[1]           { базовый случай: один элемент }
  else
  begin
    max_rest := max_recursive(arr, n - 1);   { рекурсивный случай }
    if arr[n] > max_rest then
      max_recursive := arr[n]
    else
      max_recursive := max_rest;
  end;
end;

begin
  numbers[1] := 3; numbers[2] := 7; numbers[3] := 2;
  numbers[4] := 9; numbers[5] := 1; numbers[6] := 5;
  writeln('Максимум: ', max_recursive(numbers, 6));
end.
def max_recursive(arr, n):
    # базовый случай: один элемент
    if n == 1:
        return arr[0]

    # рекурсивный случай
    max_rest = max_recursive(arr, n - 1)
    return arr[n - 1] if arr[n - 1] > max_rest else max_rest

numbers = [3, 7, 2, 9, 1, 5]
print("Максимум:", max_recursive(numbers, len(numbers)))

Логика: Максимум массива из n элементов — это больший из последнего элемента и максимума первых (n-1) элементов.

🎯 Ключевые выводы

Подведём итоги: что важно запомнить о структурном программировании.

Структурное программирование — это дисциплина мышления: разбивай сложное на простое, используй универсальные блоки Функции — основа переиспользования и модульности кода Передача параметров: по значению — копия, по ссылке (указатель в Си, var в Паскале) — сама переменная Рекурсия — мощный инструмент для задач с вложенностью (деревья, фракталы, динамическое программирование) Граничные условия — обязательны в рекурсии, иначе бесконечность! Метод «сверху вниз» — стратегия победы над сложностью: сначала план, потом детали

🤔 Проверь себя

Вопросы для самопроверки и закрепления материала

1. В чём заключается сущность структурного программирования? Какие преимущества обеспечивает эта технология?

Подумай о читаемости кода, модульности, возможности работы в команде...

2. Какие три базовые конструкции используются в структурном программировании?

Вспомни: последовательность, ветвление, цикл. Почему именно эти три?

3. Чем отличается передача параметров по значению от передачи по указателю?

Что происходит с оригинальной переменной в каждом случае?

4. Что такое рекурсия? Приведи примеры рекурсивных структур из природы или культуры.

Вспомни фракталы, матрёшки, эффект Дросте...

5. Почему в рекурсивных функциях обязательно должно быть граничное условие?

Что случится без базового случая?

6. Опиши метод разработки программы «сверху вниз». В чём его преимущества?

Как работают заглушки? Зачем сначала создавать общую структуру?

7. Зачем в C нужны прототипы функций?

Что происходит, если функция вызывается до её определения?

8. В каких случаях рекурсивное решение лучше итерационного, а в каких — хуже?

Критерии для сравнения:

  • Скорость выполнения
  • Потребление памяти
  • Читаемость кода
  • Естественность алгоритма

📝 Практические задания

Применение знаний на практике — лучший способ обучения!

✍️ Задание 1: Рекурсивная функция

Дана функция:

int F(int n) {
    if (n <= 0) return 2;
    return F(n-2) + F(n-1) + F(n/2);
}
function F(n: integer): integer;
begin
  if n <= 0 then
    F := 2
  else
    F := F(n - 2) + F(n - 1) + F(n div 2);
end;
def F(n):
    if n <= 0:
        return 2
    return F(n - 2) + F(n - 1) + F(n // 2)

Вопрос: Чему равно F(10)?

Подсказка: Построй таблицу значений от F(0) до F(10).

🎯 Задание 2: Исполнитель Калькулятор

Команды:

  • +1 (прибавить 1)
  • ×2 (умножить на 2)

Вопросы:

  1. Сколько программ превращают 1 в 20?
  2. Сколько из них проходят через число 15?

🔢 Задание 3: Модификация программы

Напиши программу на своём языке, которая:

  1. Вычисляет площадь треугольника по координатам вершин (формула Герона)
  2. Обобщает решение для n-угольника

🔍 Задание 4: Анализ рекурсии

Дана программа:

void F(int n) {
    if (n > 0) {
        F(n - 4);
        printf("%d ", n);
        F(n / 3);
    }
}

int main() {
    F(9);
    return 0;
}
procedure F(n: integer);
begin
  if n > 0 then
  begin
    F(n - 4);
    write(n, ' ');
    F(n div 3);
  end;
end;

begin
  F(9);
end.
def F(n):
    if n > 0:
        F(n - 4)
        print(n, end=" ")
        F(n // 3)

F(9)

Вопрос: Что выведет программа? Нарисуй дерево вызовов.

💻 Задание 5: Числа Фибоначчи

Напиши на своём языке:

  1. Рекурсивную функцию для вычисления чисел Фибоначчи
  2. Итеративную версию той же функции
  3. Сравни время выполнения для n = 40

🧮 Задание 6: Комбинаторика

Напиши программу вычисления биномиальных коэффициентов:

Cnk = n! / ((n-k)! × k!)

Используй функцию для вычисления факториала.

🚀 Следующие шаги

Куда двигаться дальше для углубления знаний?

📚 Изучить глубже

  • Указатели и динамическое выделение памяти (malloc, free)
  • Структуры данных (стек, очередь, связные списки)
  • Работа с файлами в C
  • Многомерные массивы и их применение

🎨 Попробовать на практике

  • Создать рекурсивный алгоритм рисования фракталов
  • Реализовать алгоритмы сортировки (быструю, пирамидальную)
  • Написать программу для работы с текстовыми файлами
  • Изучить алгоритмы на графах (поиск в глубину/ширину)

💡 Философия структурного мышления

Когда ты пишешь код, задавай себе вопросы:

  1. Можно разбить на подзадачи? → Используй функции
  2. Есть повторяющаяся структура? → Применяй циклы или рекурсию
  3. Логика понятна через месяц? → Если нет, рефактори

Эти навыки универсальны: они применимы к дизайну приложений, управлению проектами, даже к планированию жизни. Структурное мышление — это инструмент, который превращает хаос в порядок.

От хаоса к структуре — путь к мастерству

Переход от хаоса к структуре — это путь от разочарования к мастерству

🎓 Отличная работа! Теперь ты понимаешь принципы структурного программирования и можешь создавать чистый, читаемый и эффективный код на языке C. Язык C — это фундамент, на котором построено множество современных технологий. Понимание C даёт тебе контроль над памятью, понимание архитектуры и основу для изучения C++, Rust, Go.

Удачи в путешествии от кода к архитектуре! 🚀

Проверь себя

5 случайных вопросов по этому уроку — при следующем открытии будут другие. Ответы проверяются сразу, без регистрации.