Задачи модуля "Деревья": от высоты до зеркального отражения

Модуль "Деревья" • урок 11 из 11 • 5 задач • ~30 минут

Суть урока. Разбираем 5 классических алгоритмов для работы с деревьями. Практически все задачи на деревья решаются с помощью рекурсии. Ваш главный инструмент - это базовый случай (когда передан NULL) и объединение результатов обработки левого и правого поддерева. Прочитайте условие, подумайте над базовым случаем и шагом рекурсии, а затем сверяйтесь с кодом.

Задача 1. Подсчет общего числа узлов в дереве

Условие: Напишите рекурсивную функцию для подсчета общего количества узлов в заданном бинарном дереве.

Подсказка

Любое бинарное дерево состоит из корня (1 узел) плюс все узлы левого поддерева и правого поддерева. Чему равно количество узлов пустого дерева?

Разбор алгоритма

Описание алгоритма:

Используем рекурсию (Прямой обход). Формула элементарна: количество узлов в дереве = 1 (сам корень) + количество узлов в левом поддереве + количество узлов в правом поддереве.

Базовый случай: если дерево пустое (root == NULL), количество узлов равно 0.

Код на C
#include <stdio.h>

typedef struct Node {
    int data;
    struct Node* left;
    struct Node* right;
} Node;

int CountNodes(Node* root) {
    // Базовый случай
    if (root == NULL) {
        return 0;
    }
    
    // Рекурсивный шаг
    return 1 + CountNodes(root->left) + CountNodes(root->right);
}

Задача 2. Нахождение высоты (глубины) двоичного дерева

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

Подсказка

Высота дерева равна максимуму из высот левого и правого поддеревьев, плюс один (за сам корень). Какова высота пустого дерева?

Разбор алгоритма

Описание алгоритма:

Высота дерева - это максимальное расстояние от корня до самого удаленного листа. Решается рекурсивно обратным обходом (постфиксным).

  1. Сначала узнаем высоту левого поддерева.
  2. Затем узнаем высоту правого поддерева.
  3. Высота всего дерева = берем максимум из этих двух высот и прибавляем 1 (так как сам корень добавляет 1 уровень).
  4. Базовый случай: высота пустого дерева (NULL) равна 0.
Код на C
#include <stdio.h>

typedef struct Node {
    int data;
    struct Node* left;
    struct Node* right;
} Node;

// Вспомогательная функция максимума
int max(int a, int b) {
    return (a > b) ? a : b;
}

int GetHeight(Node* root) {
    if (root == NULL) {
        return 0;
    }
    
    int left_h = GetHeight(root->left);
    int right_h = GetHeight(root->right);
    
    return 1 + max(left_h, right_h);
}

Задача 3. Подсчет количества листьев

Условие: Подсчитайте количество листьев в бинарном дереве. Лист - это узел, который не имеет ни левого, ни правого потомка.

Подсказка

Если текущий узел - лист, верните 1. В противном случае верните сумму количества листьев левого и правого поддеревьев.

Разбор алгоритма

Описание алгоритма:

Лист - это узел, у которого нет ни левого, ни правого сына. Нам нужно пройтись по всему дереву и подсчитать только такие узлы.

  1. Если передан NULL, возвращаем 0.
  2. Если узел является листом (root->left == NULL && root->right == NULL), возвращаем 1.
  3. Иначе мы возвращаем сумму количества листьев из левого поддерева и количества листьев из правого поддерева.
Код на C
#include <stdio.h>

typedef struct Node {
    int data;
    struct Node* left;
    struct Node* right;
} Node;

int CountLeaves(Node* root) {
    if (root == NULL) {
        return 0;
    }
    
    // Проверка на лист
    if (root->left == NULL && root->right == NULL) {
        return 1;
    }
    
    // Считаем листья в поддеревьях
    return CountLeaves(root->left) + CountLeaves(root->right);
}

Задача 4. Проверка двух деревьев на идентичность

Условие: Напишите функцию, проверяющую два двоичных дерева на полную идентичность: они должны иметь одинаковую структуру, а соответствующие узлы - одинаковые значения.

Подсказка

Сначала проверьте базовые случаи: оба пусты или одно пустое. Если оба не пустые, сравните значения в корне и рекурсивно вызовите проверку для поддеревьев.

Разбор алгоритма

Описание алгоритма:

Два дерева считаются идентичными, если они совпадают и структурно, и по значениям во всех узлах.

Логика проверки:

  1. Если оба дерева пустые, они равны (возвращаем true).
  2. Если одно пустое, а другое нет - они не равны (возвращаем false).
  3. Если оба не пустые, проверяем: равны ли значения их корней? Если да, рекурсивно проверяем на равенство их левые поддеревья И правые поддеревья.
Код на C
#include <stdio.h>
#include <stdbool.h>

typedef struct Node {
    int data;
    struct Node* left;
    struct Node* right;
} Node;

bool AreIdentical(Node* tree1, Node* tree2) {
    // 1. Оба дерева пустые
    if (tree1 == NULL && tree2 == NULL) return true;
    
    // 2. Одно из деревьев пустое, а другое нет
    if (tree1 == NULL || tree2 == NULL) return false;
    
    // 3. Сверяем данные и рекурсивно спускаемся вниз
    return (tree1->data == tree2->data) 
        && AreIdentical(tree1->left, tree2->left) 
        && AreIdentical(tree1->right, tree2->right);
}

Задача 5. Зеркальное отражение дерева (Mirroring)

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

Подсказка

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

Разбор алгоритма

Описание алгоритма:

Требуется модифицировать существующее дерево так, чтобы оно стало своим зеркальным отражением. Это означает, что для каждого узла в дереве мы должны поменять местами его левого и правого сына.

Это классическая задача, которую часто дают на собеседованиях (знаменитый мем "Инвертируй бинарное дерево"). Мы используем обратный обход (ЛПК) или прямой: достаточно поменять местами указатели left и right, а затем рекурсивно вызвать функцию для обоих детей.

Зеркальное отражение двоичного дерева: левый и правый сын меняются местами в каждом узле
Отражение переставляет указатели, а данные с места не двигаются
Код на C
#include <stdio.h>

typedef struct Node {
    int data;
    struct Node* left;
    struct Node* right;
} Node;

void MirrorTree(Node* root) {
    if (root == NULL) {
        return;
    }
    
    // Сначала рекурсивно отражаем поддеревья
    MirrorTree(root->left);
    MirrorTree(root->right);
    
    // Меняем местами левого и правого сына текущего узла
    Node* temp = root->left;
    root->left = root->right;
    root->right = temp;
}
Как вам задачи? Спасибо за отзыв!