Java for Beginner
869 subscribers
1.01K photos
275 videos
14 files
1.69K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Что такое HashSet и как он реализован внутри? 🤓

Ответ:

HashSet
— это коллекция, которая не хранит дубликаты элементов.

Внутри он реализован на основе HashMap, где значение элемента HashSet становится ключом в этой HashMap, а в качестве значения в HashMap используется фиктивный объект-заглушка (static final Object PRESENT = new Object()).

Таким образом, все уникальность и быстрый доступ (O(1) в среднем) обеспечиваются за счет механизмов хеширования, как в HashMap. Порядок элементов не гарантируется.



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
История IT-технологий сегодня — 30 января

ℹ️ Кто родился в этот день

Ду́глас Карл Энгельба́рт (англ. Douglas Carl Engelbart; 30 января 1925, Портленд (Орегон) — 2 июля 2013, Атертон, Калифорния) — один из первых исследователей человеко-машинного интерфейса и изобретатель компьютерной мыши. В ряду других его изобретений — графический пользовательский интерфейс, гипертекст, текстовый редактор, групповые онлайн-конференции.


🌐 Знаковые события

1982 — первый массовый вирус для ПК Elk Cloner: 15‑летний Ричард Скрента написал вирус для Apple II, распространявшийся через загрузочные дискеты; это один из первых широко распространённых примеров самораспространяющегося кода и важная веха в истории компьютерных вирусов и антивирусов.

2007 — релиз Microsoft Windows Vista: новая версия Windows с UAC, Aero, изменённой моделью драйверов и улучшенной безопасностью; несмотря на спорную репутацию, Vista принесла в экосистему Windows ряд фундаментальных изменений, которые позже доехали в более доведённом виде до Windows 7 и дальше.

2009 — с космодрома Плесецк с помощью ракеты-носителя «Циклон-3» запущен российский космический аппарат «Коронас-Фотон».


#Biography #Birth_Date #Events #30Января
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5
Раздел 7. Алгоритмы

Глава 5: Рекурсия, деревья и введение в динамическое программирование


От рекурсии к динамическому программированию

Ранее мы рассмотрели рекурсию как естественный инструмент для работы с иерархическими структурами. Но в мире вычислений есть задачи, где прямое применение рекурсии превращает элегантный код в тормозной механизм, пожирающий процессорное время и память. Классический пример такого провала — вычисление чисел Фибоначчи.

Числа Фибоначчи определяются простейшей рекуррентной формулой: F(0) = 0, F(1) = 1, а для n > 1 выполняется F(n) = F(n-1) + F(n-2). Это определение так и просится быть преобразованным в рекурсивную функцию. Однако наивная реализация скрывает в себе экспоненциальную бомбу. Мы разберем, почему это происходит, как мемоизация превращает катастрофу в триумф, и как эти идеи ложатся в основу парадигмы динамического программирования.


Наивная рекурсия и экспоненциальная сложность

Вот как выглядит буквальная имплементация определения Фибоначчи:
public class NaiveFibonacci {

/**
* Вычисляет n-е число Фибоначчи наивной рекурсией.
*
* @param n порядковый номер, начиная с 0 (F(0)=0, F(1)=1)
* @return n-е число Фибоначчи
* @throws IllegalArgumentException если n отрицательный
*/
public static long fibonacci(int n) {
// Базовые случаи, соответствующие математическому определению
if (n < 0) {
throw new IllegalArgumentException("n must be non-negative");
}
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}

// Рекурсивный шаг: F(n) = F(n-1) + F(n-2)
// Ключевой момент: два вызова на каждом уровне
return fibonacci(n - 1) + fibonacci(n - 2);
}
}


Код выглядит правильно, компилируется и дает правильные результаты для малых n. Но попробуйте вызвать fibonacci(50) и приготовьтесь ждать. Не секунды, а минуты. А fibonacci(100) на обычном железе не завершится за разумное время.

Дерево вызовов: визуализация катастрофы

Чтобы понять, почему это происходит, нужно построить дерево вызовов.

Для fibonacci(5) оно выглядит так:
fibonacci(5)
├── fibonacci(4)
│ ├── fibonacci(3)
│ │ ├── fibonacci(2)
│ │ │ ├── fibonacci(1) = 1
│ │ │ └── fibonacci(0) = 0
│ │ ├── fibonacci(1) = 1
│ │ └── 1 + 1 = 2
│ ├── fibonacci(2)
│ │ ├── fibonacci(1) = 1
│ │ └── fibonacci(0) = 0
│ └── 2 + 1 = 3
├── fibonacci(3)
│ ├── fibonacci(2)
│ │ ├── fibonacci(1) = 1
│ │ └── fibonacci(0) = 0
│ ├── fibonacci(1) = 1
│ └── 1 + 1 = 2
└── 3 + 2 = 5


Заметьте: fibonacci(2) вычисляется три раза, fibonacci(3) — дважды. При увеличении n это дублирование становится катастрофическим. Дерево вызовов — не дерево, а граф, где один и тот же узел достигается множеством путей. Но наивная рекурсия это не знает и вычисляет повторно каждый раз.


Математический анализ сложности: O(2ⁿ)

Количество вызовов функции можно описать рекуррентным соотношением T(n) = T(n-1) + T(n-2) + O(1), что очень похоже на саму последовательность Фибоначчи. Можно доказать индукцией, что T(n) ≥ F(n). А поскольку F(n) ≈ φⁿ / √5, где φ ≈ 1.618 — золотое сечение, получаем экспоненциальный рост.

Более грубая, но наглядная оценка: пусть каждый вызов порождает два новых. Тогда на глубине n будет не более 2ⁿ узлов. Действительное число вызовов немного меньше из-за сокращения базовых случаев, но порядок остается экспоненциальным — O(2ⁿ).

Практические измерения (на JVM с отключенным JIT для чистоты эксперимента):
fibonacci(30) — ~2.7 миллиона вызовов, ~10 мс
fibonacci(40) — ~3.3 миллиарда вызовов, ~1000 мс
fibonacci(50) — ~4.1 триллионов вызовов, ~120 секунд
fibonacci(100) — вызовов больше, чем атомов на Земле

Это не медленный алгоритм — это неработающий алгоритм. Любая задача, где n может быть 50 или больше, становится неприемлемой.


#Java #для_новичков #beginner #algorithm #recursion
👍4
Мемоизация — кэширование как спасение

Мемоизация
— это техника сохранения результатов дорогих вызовов функций и повторного использования вместо повторного вычисления. Это не специфично для рекурсии, но в контексте рекурсивных алгоритмов она трансформирует экспоненциальное время в линейное.

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

Реализация наивной мемоизации через HashMap
import java.util.HashMap;
import java.util.Map;

public class MemoizedFibonacci {

// Кэш для хранения вычисленных значений. Map<аргумент, результат>
// Используем long для n, т.к. int может переполниться при больших значениях
private static final Map<Long, Long> memo = new HashMap<>();

// Инициализация базовых случаев в кэше
static {
memo.put(0L, 0L);
memo.put(1L, 1L);
}

/**
* Потоко-НЕБЕЗОПАСНАЯ версия. Для многопоточности использовать ConcurrentHashMap.
*/
public static long fibonacci(long n) {
if (n < 0) {
throw new IllegalArgumentException("n must be non-negative");
}

// Проверка кэша: если значение уже вычислено, возвращаем мгновенно
if (memo.containsKey(n)) {
return memo.get(n);
}

// Рекурсивный шаг с мемоизацией
// Вычисляем только один раз, результат сохраняется в memo
long result = fibonacci(n - 1) + fibonacci(n - 2);

// Сохраняем в кэш перед возвратом
memo.put(n, result);

return result;
}

/**
* Метод для сброса кэша в тестах. В продакшене так делать не стоит.
*/
public static void clearCache() {
memo.clear();
memo.put(0L, 0L);
memo.put(1L, 1L);
}
}


Что изменилось в дереве вызовов?

При fibonacci(5) первый вызов вычислит все подзначения от 0 до 5 и сохранит их в memo. Последующие вызовы из глубины рекурсии не пойдут дальше одного уровня, так как fibonacci(3) уже в кэше. Дерево вызовов превращается в прямолинейный граф с N узлов.

Анализ сложности:
Время: O(n). Каждое значение от 0 до n вычисляется ровно один раз. Всего n+1 вычислений, каждое — O(1).
Память: O(n) на кэш + O(h) на стек вызовов. Для Фибоначчи h = n в худшем случае, так что суммарно O(n). Но теперь память расходуется в куче (heap), где ограничения гораздо мягче стека.

На практике fibonacci(100) выполняется за < 1 мс, а fibonacci(1000) — за несколько микросекунд, хотя результат уже не помещается в long (можно использовать BigInteger).


Потоковая безопасность и жизненный цикл кэша


В примере выше memo — это статическое поле, общее для всех вызовов.

Это создает несколько проблем:
Потоконебезопасность: Одновременный вызов из двух потоков может повредить HashMap. Решение: ConcurrentHashMap или ThreadLocal<Map>.
Утечка памяти: Кэш живет вечно, захватывая память. Решение: использовать WeakHashMap или явный Cache из библиотеки (Caffeine, Guava).
Тестируемость: Тесты влияют друг на друга через общий кэш. Решение: инстанцировать кэш вне метода и передавать как параметр ( dependency injection).

Правильный production-ready подход:
public class FibonacciService {
private final Map<Long, Long> memo = new ConcurrentHashMap<>();

public FibonacciService() {
memo.put(0L, 0L);
memo.put(1L, 1L);
}

public long fibonacci(long n) {
// computeIfAbsent — атомарная операция, потокобезопасная
return memo.computeIfAbsent(n, key -> fibonacci(key - 1) + fibonacci(key - 2));
}
}



#Java #для_новичков #beginner #algorithm #recursion
👍2🔥1
Динамическое программирование сверху вниз

Мемоизация — это техника.

Динамическое программирование (Dynamic Programming, DP) — это парадигма решения задач оптимизации, основанная на двух принципах:
Оптимальная подструктура: Оптимальное решение задачи содержит в себе оптимальные решения подзадач. Для Фибоначчи: F(n) оптимально вычисляется через оптимальные F(n-1) и F(n-2).
Перекрывающиеся подзадачи: Разные подзадачи содержат одни и те же еще более мелкие подзадачи. Фибоначчи — идеальный пример: F(n-1) и F(n-2) оба нуждаются в F(n-3).

Top-down DP (сверху вниз) — это именно рекурсия с мемоизацией. Мы начинаем с верхней задачи и по мере погружения кэшируем результаты.

Инвариант динамического программирования

В любом DP-алгоритме существует таблица состояний (state table). В мемоизированной версии это Map. Важно правильно определить ключ состояния — минимальный набор параметров, однозначно определяющих подзадачу. Для Фибоначчи это просто n. Для более сложных задач это может быть кортеж параметров.

Bottom-up подход: альтернатива top-down

Есть и другой способ DP — bottom-up (снизу вверх).

Мы не рекурсивно спускаемся, а итеративно строим таблицу от базовых случаев к цели:
public static long fibonacciBottomUp(long n) {
if (n < 0) throw new IllegalArgumentException();
if (n == 0) return 0;
if (n == 1) return 1;

long[] dp = new long[(int)n + 1];
dp[0] = 0;
dp[1] = 1;

for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}

return dp[(int)n];
}


Для Фибоначчи можно сжать память до O(1), храня только два последних значения:
public static long fibonacciOptimized(long n) {
if (n < 0) throw new IllegalArgumentException();
if (n == 0) return 0;

long a = 0, b = 1;
for (long i = 2; i <= n; i++) {
long next = a + b;
a = b;
b = next;
}
return b;
}


Это уже не DP, а простая математическая оптимизация, но она показывает, что DP — промежуточный этап от наивной рекурсии к оптимальному решению.


Практические применения DP в библиотечных системах

Задача: оптимальное размещение книг на полках


Представьте: у вас есть полки разной высоты и книги разной толщины. Нужно разместить книги так, чтобы минимизировать пустое пространство. Это вариация задачи о рюкзаке с ограничениями.
/**
* Динамическое программирование для оптимального размещения.
*
* Состояние: dp[i][w] = минимальное незаполненное пространство
* после размещения первых i книг на полках общей ширины w.
*/
public class ShelfOptimizer {

public static int optimize(List<Integer> bookThicknesses, int shelfWidth) {
int n = bookThicknesses.size();
int[][] dp = new int[n + 1][shelfWidth + 1];

// Инициализация
for (int w = 0; w <= shelfWidth; w++) {
dp[0][w] = w; // Без книг осталось все свободное пространство
}

// Заполнение таблицы
for (int i = 1; i <= n; i++) {
int thickness = bookThicknesses.get(i - 1);
for (int w = 0; w <= shelfWidth; w++) {
if (thickness > w) {
// Книга не помещается — переносим предыдущее состояние
dp[i][w] = dp[i - 1][w];
} else {
// Выбираем лучшее: либо не кладем книгу, либо кладем
dp[i][w] = Math.min(
dp[i - 1][w], // Не кладем
dp[i - 1][w - thickness] // Кладем, освобождаем оставшееся место
);
}
}
}

return dp[n][shelfWidth];
}
}


Это классический bottom-up DP: строим таблицу, каждая ячейка определяется предыдущими.

#Java #для_новичков #beginner #algorithm #recursion
👍1
Поиск кратчайшего пути в графе книг

Если книги связаны рекомендациями «после прочтения этой, читайте эту», можно найти кратчайшую цепочку от книги A до книги B через DP:

/**
* Определение длины кратчайшего пути от start до end.
* dp[v] = минимальная длина пути от start до v.
*/
public static int shortestPathLength(Map<String, List<String>> graph, String start, String end) {
Map<String, Integer> dp = new HashMap<>();
dp.put(start, 0);

// BFS + DP гибрид
Queue<String> queue = new ArrayDeque<>();
queue.add(start);

while (!queue.isEmpty()) {
String current = queue.poll();
int currentDist = dp.get(current);

if (current.equals(end)) {
return currentDist;
}

for (String neighbor : graph.getOrDefault(current, List.of())) {
if (!dp.containsKey(neighbor) || dp.get(neighbor) > currentDist + 1) {
dp.put(neighbor, currentDist + 1);
queue.add(neighbor);
}
}
}

return -1; // Путь не найден
}

Это пример DP, где состояние — это вершина графа.


Подводные камни и best practices

1. Выбор ключа состояния
Ключ должен быть минимальным и однозначным. Для задачи «подсчет путей в сетке» ключ — это (x, y). Для задачи «редактирование строки» — (i, j), где i и j — позиции в строках. Если ключ слишком большой (содержит лишние поля), кэш раздувается. Если неоднозначный — получите неверные результаты.

2. Порядок вычислений и рекурсивные циклы
Если подзадачи зависят друг от друга (например, в графе с циклами), naive top-down DP может зациклиться. Решение:
Добавить состояние "в процессе" в кэш и бросать исключение при повторном входе
Использовать bottom-up
Применять алгоритмы для графов с циклами (topological sort)

3. Память vs время
DP всегда торгует памятью на время. Для задач с большим количеством состояний (например, dp[1000][1000][1000]) память может стать bottlenek'ом. Тогда применяют:
Оптимизацию памяти: хранить только нужные строки таблицы
Встречный поиск (meet-in-the-middle): разбить задачу на две половины
Аппроксимацию: отказаться от точного DP в пользу эвристик

4. Не всё можно решить DP

DP требует оптимальной подструктуры и перекрывающихся подзадач. Если решение задачи не складывается из решений подзадач (например, задача о ранце с взаимозависимыми вещами), DP неприменим.

5. Состояние гонки в мемоизации
В многопоточной среде используйте ConcurrentHashMap.computeIfAbsent, который атомарно вычисляет значение, если его нет. Но будьте осторожны: рекурсивный вызов внутри лямбды может заблокировать bucket и вызвать deadlock при рекурсивных зависимостях.

Раздел 6: От DP к современным практикам
Кэширование в распределенных системах
В микросервисах, управляющих библиотекой, DP-подход применяется через распределенный кэш (Redis, Memcached). Состояние — ключ в Redis, рекурсия — это серийный вызов сервисов. Но теперь нужно думать о сетевых задержках, а не только о процессоре.
Машинное обучение и DP
В NLP для разбора предложений используется CYK-алгоритм — DP по грамматике. В компьютерном зрении для выравнивания изображений — динамическое искажение времени (DTW). DP — фундаментальная парадигма не только в классических алгоритмах.

#Java #для_новичков #beginner #algorithm #recursion
👍1
Что выведет код?

import java.util.HashMap;
import java.util.Map;

public class Task300126 {
private static Map<Integer, Integer> cache = new HashMap<>();

public static void main(String[] args) {
System.out.println(fib300126(5));
System.out.println(fib300126(3));
System.out.println(fib300126(5));
}

static int fib300126(int n) {
if (cache.containsKey(n)) {
return cache.get(n);
}

int result;
if (n <= 1) {
result = n;
} else {
result = fib300126(n - 1) + fib300126(n - 2);
}

cache.put(n, result);
return result;
}
}


#Tasks
👍1
Варианты ответа:
Anonymous Quiz
46%
5 2 5
8%
5 2 2
31%
8 3 8
15%
5 3 5
👍1
Comparator и Comparable. В чем разница? 🤓

Ответ:

Оба интерфейса используются для сравнения и сортировки объектов.

Comparable определяет естественный порядок сортировки объектов класса. Он содержит один метод compareTo(T o) и реализуется внутри самого класса, который нужно сортировать.

Comparator — это отдельный класс, реализующий интерфейс с методом compare(T o1, T o2). Он определяет альтернативный (внешний) порядок сортировки, не изменяя исходный класс.

Можно создавать множество Comparator для разных видов сортировки.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍1
История IT-технологий сегодня —31 января

ℹ️ Кто родился в этот день

Гвидо ван Россум (нид. Guido van Rossum; род. 31 января 1956, Гаага, Нидерланды) — голландский программист, прежде всего известный как автор языка программирования Python. Среди разработчиков Python Гвидо известен как «великодушный пожизненный диктатор» проекта, что означает, что он продолжает наблюдать за процессом разработки Python, принимая окончательные решения, когда это необходимо. С июля 2018 года Гвидо ушёл в постоянный отпуск от диктаторства, оставив за собой право быть обычным разработчиком. До разработки Python участвовал в проекте по написанию языка для обучения программированию — ABC. Лауреат «Free Software Award» 2001 года. Покинув в декабре 2012 года корпорацию Google, с 2013 года работал в компании Dropbox Inc, выйдя на пенсию в 2019, а в ноябре 2020 года присоединился к одному из отделов корпорации Microsoft.

Ирма М. Уайман (31 января 1928 г. — 17 ноября 2015 г.) одна из первых инженеров-программистов и первая женщина, ставшая вице-президентом компании Honeywell, Inc. Она была преподавателем системного мышления и первой женщиной-главным ИТ-директором Honeywell, Inc., которая тогда входила в список Fortune 100.


🌐 Знаковые события

1958 — запущен «Эксплорер-1», первый американский спутник.


#Biography #Birth_Date #Events #31Января
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
С 24.01 по 30.01
Предыдущий пост(С 17.12 по 23.01)

Воскресный мотивационный пост:
Потом не существует. Есть только сегодня и никогда

Запись встреч/видео:
3. Data Access Layer: осознанный выбор между JPA, JDBC и jOOQ

Обучающие статьи:
Java:
Раздел 7. Алгоритмы

Глава 4: Эффективный поиск. Бинарный и не только
Модификации и родственные методы
Практика

Глава 5: Рекурсия, деревья и введение в динамическое программирование
Принцип рекурсии и её опасности
Рекурсия на практике: обход деревьев
От рекурсии к динамическому программированию

Полезные статьи и видео:
Как компьютер понимает Языки программирования: история о том, как ваш код превращается в нули и единицы


Как и всегда, задачи можно найти под тегом - #Tasks, вопросы с собеседований - #собеседование
👍2
Жизненно
🔥9
История IT-технологий сегодня — 01 Февраля

ℹ️ Кто родился в этот день

Не нашел(


🌐 Знаковые события

1982 компания Intel выпустила первый Intel 80286 — 16-разрядный процессор второго поколения архитектуры x86.


#Biography #Birth_Date #Events #01февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
А вы читаете этот канал? Давайте посчитаем сколько нас из 800, реально заходят и читают посты?
Anonymous Poll
81%
Я читаю!
9%
Я не читаю, но голос оставлю)
9%
Я есть Грут
🔥4
4. Spring Boot Actuator: телеметрия вашего микросервиса

В этом видео мы превращаем OrderHub из чёрного ящика в наблюдаемую production-систему.
Spring Boot Actuator — это не просто библиотека, а стандартный Runtime API, через который микросервис общается с платформой (Kubernetes, мониторинг, SRE).

Что вы узнаете:
🔹 Health как контракт с оркестратором
- Как Kubernetes использует /health для liveness и readiness probes
- Иерархия HealthIndicators: от диска до бизнес-логики
- Что происходит, когда база данных "падает"

🔹 Metrics — язык общения с платформой
- Как Micrometer и Actuator работают вместе
- Four Golden Signals для SRE: latency, traffic, errors, saturation
- Почему метрики должны быть с тегами

🔹 Info — паспорт сервиса в распределённой системе
- Immutable metadata для быстрой диагностики инцидентов

🔹 Advanced endpoints — инструменты последней линии обороны
- Для чего и когда испольщовать /env, /heapdump, /threaddump
- Динамическое управление логированием через /loggers
- Почему эти инструменты опасны в неверных руках

Исходный код проекта на GitHub очень ждет Ваших звезд.

Ссылка на Youtube
Ссылка на Рутьюб

Смотрите, ставьте лайки, подписывайтесь на каналы!✌️
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥1
История IT-технологий сегодня — 02 Февраля

ℹ️ Кто родился в этот день

Джон Генри Холланд (англ. John Henry Holland; 2 февраля 1929, Форт-Уэйн — 9 августа 2015, Анн-Арбор)американский учёный, профессор психологии, профессор электротехники и информатики в Мичиганском университете, Анн-Арбор. Один из первых учёных, начавших изучать сложные системы и нелинейную науку; известен как отец генетических алгоритмов.

Ральф Чарльз Меркл (англ. Ralph Charles Merkle; родился 2 февраля 1952, Беркли, Калифорния, США)американский криптограф, один из создателей криптографии с открытым ключом (схема Меркла, деревья Меркла и др.); деревья Меркла являются базовым примитивом в блокчейнах, системах проверки целостности данных, файловых системах и ряде протоколов безопасности.


🌐 Знаковые события

2000 – первая в Европе цифровая кинопроекция (Париж), реализованная Филиппом Бинаном с использованием технологии DLP CINEMA, разработанной компанией Texas Instruments .


#Biography #Birth_Date #Events #02февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Раздел 7. Алгоритмы

Глава 5: Рекурсия, деревья и введение в динамическое программирование

Практическое применение рекурсии в библиотечной системе

Цели:
Освоить принципы работы с древовидными структурами
Научиться применять рекурсию для обхода иерархических данных
Сравнить рекурсивные и итеративные подходы
Понять оптимизацию рекурсии через мемоизацию


Часть 1: Моделирование древовидной структуры жанров

Постановка задачи:
В нашей библиотеке жанры книг образуют иерархию.

Например:
Литература (корень)
Художественная литература
Фантастика
Научная фантастика
Фэнтези
Нехудожественная литература

Вам нужно: Создать структуру данных, которая позволит хранить такую иерархию.

Шаг 1.1: Создайте класс Genre

Что нужно сделать:

Определите, какие поля понадобятся для хранения:
Название жанра
Список поджанров (дочерние элементы)
Список книг, относящихся к этому жанру

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

Вопрос для размышления: Почему важно, чтобы поджанры хранились как список объектов Genre?

Шаг 1.2: Свяжите книги с жанрами

Что нужно сделать:

Обновите класс Book:
Добавьте поле для хранения основного жанра книги
Измените конструктор для установки жанра

Подумайте: Может ли книга принадлежать к нескольким жанрам? Как это реализовать?

Шаг 1.3: Построение дерева жанров

Практическое задание:

В классе Library создайте метод initGenres(), который:
Создаёт корневой жанр "Литература"
Добавляет 3-4 уровня вложенности жанров
Распределяет существующие книги по соответствующим жанрам

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



Часть 2: Рекурсивный подсчёт книг в жанре

Задача: Напишите метод, который считает все книги в жанре и его поджанрах

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

Пример:
Книг в "Фантастике" = 
книги в "Фантастике" +
книги в "Научной фантастике" +
книги в "Фэнтези"


Шаг 2.1: Реализуйте метод countBooksRecursive()

Что нужно сделать:

Добавьте метод в класс Genre
Реализуйте рекурсивный подсчёт по описанному алгоритму
Протестируйте на разных уровнях дерева

Контрольные вопросы:
Что произойдёт, если в дереве будет циклическая ссылка?
Какова максимальная глубина рекурсии для вашего дерева?


Часть 3: Сравнение рекурсивного и итеративного обхода

Задача: Вывести структуру дерева жанров двумя способами

Шаг 3.1: Рекурсивный обход (Pre-order)

Алгоритм:
Обработать текущий узел (вывести информацию)
Рекурсивно обработать все дочерние узлы

Что нужно сделать:
Реализуйте метод printTreeRecursive(Genre genre, int depth), где depth используется для создания отступов при выводе.

Шаг 3.2: Итеративный обход с использованием стека

Можно эмулировать рекурсию с помощью стека:
Помещаем корневой элемент в стек

Пока стек не пуст:
Извлекаем элемент
Обрабатываем его
Добавляем в стек его детей

Что нужно сделать:
Реализуйте метод printTreeIterative(Genre root) с использованием Stack<Genre>.

Вопрос: Почему детей нужно добавлять в стек в обратном порядке?

Шаг 3.3: Сравнение подходов

Практическое задание:
Создайте дерево глубиной 10 уровней
Замерьте время выполнения обоих методов

Ответьте на вопросы:
Какой подход читаемее?
Какой безопаснее для глубоких деревьев?
Когда стоит использовать каждый из них?


#Java #для_новичков #beginner #algorithm #recursion #практика
👍3
Часть 4: Числа Фибоначчи - три подхода

Задача: Вычислить n-ное число Фибоначчи

Определение:
F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2)

Шаг 4.1: Наивная рекурсия

Что нужно сделать:
Реализуйте метод fibRecursive(int n) по определению.

Анализ проблемы:
Нарисуйте дерево вызовов для n=5. Сколько раз вычисляется F(2)? F(3)?

Задание: Замерьте время выполнения для n=40. Почему это так долго?

Шаг 4.2: Мемоизация (Top-down динамическое программирование)

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

Что нужно сделать:
Создайте HashMap<Integer, Long> для хранения вычисленных значений

Измените рекурсивную функцию так, чтобы она:

Проверяла, нет ли значения в кэше
Сохраняла новое значение после вычисления

Вопрос: Как изменилось дерево вызовов?

Шаг 4.3: Итеративное решение (Bottom-up)

Алгоритм:
Если n <= 1: вернуть n
Иначе: последовательно вычислить все числа от 2 до n

Что нужно сделать:
Реализуйте метод fibIterative(int n) без рекурсии.

Задание: Сравните производительность всех трёх методов для n=50.


Итоговые задания

Задание 1: Рефакторинг
Перепишите один из рекурсивных методов в итеративный. Объясните, что изменилось в сложности кода.

Задание 2: Оптимизация
Добавьте проверку на циклические ссылки в дереве жанров. Что произойдёт без этой проверки?

Задание 3: Эксперимент
Создайте глубокое дерево (20+ уровней). Протестируйте оба метода обхода. При какой глубине возникает StackOverflowError?

Задание 4: Документация

Напишите комментарии к методам, объясняющие:
Сложность алгоритма (Big O)
Ограничения (максимальная глубина)
Рекомендации по использованию


#Java #для_новичков #beginner #algorithm #recursion #практика
👍4