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
Требование предсказуемого времени доступа: TreeMap (гарантирует O(log n))

Амортизированная сложность: анализ средней стоимости операций

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

Динамический массив (ArrayList): классический пример

Реализация ArrayList в Java демонстрирует ключевые принципы амортизированного анализа.

Рассмотрим внутреннее устройство и стратегию роста:

// Упрощенная реализация ArrayList для демонстрации принципов
public class AmortizedArrayList<E> {
private static final int DEFAULT_CAPACITY = 10;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private Object[] elementData;
private int size;

public AmortizedArrayList() {
this.elementData = new Object[DEFAULT_CAPACITY];
this.size = 0;
}

// Амортизированная сложность добавления: O(1)
public boolean add(E element) {
ensureCapacityInternal(size + 1); // Может вызвать дорогое расширение
elementData[size++] = element;
return true;
}

private void ensureCapacityInternal(int minCapacity) {
if (minCapacity - elementData.length > 0) {
// Требуется расширение массива
grow(minCapacity);
}
}

private void grow(int minCapacity) {
int oldCapacity = elementData.length;

// Стандартная стратегия роста: увеличение на 50%
int newCapacity = oldCapacity + (oldCapacity >> 1);

if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}

if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}

// Самая дорогая операция: копирование всех элементов
elementData = Arrays.copyOf(elementData, newCapacity);

// В этот момент выполняется O(n) операций
// Но эта стоимость распределяется (амортизируется) по предыдущим дешевым операциям
}

// Метод получения элемента: всегда O(1)
@SuppressWarnings("unchecked")
public E get(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException();
}
return (E) elementData[index];
}
}


Анализ амортизированной стоимости

Для анализа амортизированной сложности операции add() рассмотрим последовательность из n операций добавления.

Метод агрегирования (учетных издержек):

Каждая обычная операция add() стоит 1 единицу (запись в массив)

При расширении массива с capacity k до capacity 1.5k:
Копирование k элементов: k единиц
Эти k единиц "распределяются" по предыдущим k/2 операциям
Амортизированная стоимость на операцию: (стоимость всех операций) / n

Рассмотрим рост массива по мере добавления элементов:
Начальная capacity: 10
Расширения при: 10 → 15 → 22 → 33 → 49 → 73 → 109 → ...

Последовательность стоимостей для 100 операций add():
90 операций стоят 1 единицу
10 операций расширения стоят: 10 + 15 + 22 + 33 + 49 + 73 + 109 = 311 единиц
Общая стоимость: 90 + 311 = 401 единиц
Амортизированная стоимость на операцию: 401 / 100 ≈ 4.01

Таким образом, хотя некоторые операции очень дороги (O(n)), их средняя стоимость в длинной последовательности остается константной O(1).

Метод потенциала: формальный анализ

Более формальный подход — метод потенциала. Определим потенциал Φ как величину, пропорциональную разнице между capacity и size:
Φ = 2 × (capacity - size)
При каждой операции add():
Фактическая стоимость: 1 (обычная запись) или k+1 (при расширении)
Изменение потенциала: ΔΦ
Амортизированная стоимость: фактическая стоимость + ΔΦ

Можно доказать, что амортизированная стоимость каждой операции ограничена константой, что формально доказывает O(1) амортизированную сложность.


#Java #для_новичков #beginner #algorithm #bigO
👍2
Практические следствия амортизированного анализа

Если известно примерное количество элементов, можно задать начальную capacity, избежав нескольких расширений:
// Плохо: множественные расширения при добавлении 1000 элементов
List<Integer> list = new ArrayList<>(); // capacity=10
for (int i = 0; i < 1000; i++) {
list.add(i); // Расширения при 10...
}

// Хорошо: одно расширение или вообще без расширений
List<Integer> optimizedList = new ArrayList<>(1000); // capacity=1000
for (int i = 0; i < 1000; i++) {
optimizedList.add(i); // Без расширений
}



Амортизация в других структурах: Принцип амортизации применяется в:

Динамических таблицах хеширования: расширение buckets
Стеках с мультипопом: операция multipop
Деревьях со сбалансированным слиянием: операции union-find


Реальные компромиссы в современных системах

Кэширование — это практическое применение компромисса время-память. Различные стратегии кэширования представляют разные точки на спектре этого компромисса.
public class CacheTradeoff {
// LRU (Least Recently Used) кэш
// Использует больше памяти для поддержания порядка доступа
public static class LRUCache<K, V> {
private final int capacity;
private final Map<K, V> map;
private final Deque<K> accessOrder;

public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new HashMap<>(capacity);
this.accessOrder = new LinkedList<>();
}

public V get(K key) {
V value = map.get(key);
if (value != null) {
// Обновляем порядок: O(n) операция
accessOrder.remove(key);
accessOrder.addFirst(key);
}
return value;
}

public void put(K key, V value) {
if (map.size() >= capacity) {
// Удаляем наименее использованный
K lruKey = accessOrder.removeLast();
map.remove(lruKey);
}
map.put(key, value);
accessOrder.addFirst(key);
}
}

// Простой кэш с фиксированным размером
// Использует меньше памяти, но может иметь хуже hit ratio
public static class SimpleCache<K, V> {
private final Map<K, V> map;
private final int maxSize;

public SimpleCache(int maxSize) {
this.maxSize = maxSize;
this.map = new HashMap<>(maxSize);
}

public V get(K key) {
return map.get(key); // O(1), но нет обновления порядка
}

public void put(K key, V value) {
if (map.size() >= maxSize) {
// Удаляем случайный элемент (на практике сложнее)
Iterator<K> it = map.keySet().iterator();
if (it.hasNext()) {
map.remove(it.next());
}
}
map.put(key, value);
}
}
}


Сжатие данных: время на распаковку vs экономия памяти


Сжатие данных — это еще одна форма компромисса, где экономия памяти достигается за счет времени на сжатие и распаковку.
public class CompressionTradeoff {
// Быстрое сжатие (LZ4), но меньшая степень сжатия
public byte[] compressFast(byte[] data) {
// LZ4: высокая скорость, средняя степень сжатия
// Подходит для компрессии "на лету"
return lz4Compress(data);
}

// Медленное сжатие (Zstandard с высоким уровнем), но лучшее сжатие
public byte[] compressHigh(byte[] data) {
// Zstandard уровень 19: медленно, но максимальное сжатие
// Подходит для архивов, где сжатие выполняется один раз
return zstdCompress(data, 19);
}

// Выбор стратегии в зависимости от контекста
public byte[] compressAdaptive(byte[] data, boolean prioritizeSpeed) {
if (prioritizeSpeed) {
return compressFast(data); // Для сетевой передачи
} else {
return compressHigh(data); // Для долговременного хранения
}
}
}


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

import java.util.TreeSet;

public class Task130126 {
static class Item130126 implements Comparable<Item130126> {
int value;
Item130126(int value) { this.value = value; }

public int compareTo(Item130126 other) {
return Integer.compare(this.value, other.value);
}
}
public static void main(String[] args) {
TreeSet<Item130126> set = new TreeSet<>();
Item130126 item1 = new Item130126(1);
Item130126 item2 = new Item130126(2);
set.add(item1);
set.add(item2);
item1.value = 3;
System.out.println(set.contains(item1));
System.out.println(set.contains(item2));
set.remove(item1);
System.out.println(set.size());
}
}


#Tasks
👍1
Варианты ответа:
Anonymous Quiz
43%
true true 1
36%
false true 2
14%
true false 1
7%
false false 2
👍1
Почему @Transactional не работает при self-invocation? 🤓

Ответ:

Spring использует прокси.

Вызов метода внутри того же класса минует прокси, поэтому транзакция не создаётся.

Это частая ошибка, которую проверяют на собеседованиях.


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


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

Никого не нашел(


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

2005 — космический зонд «Гюйгенс» совершил посадку на Титан.

2020 — прекращение расширенной технической поддержки Windows 7 и Windows 10 Mobile.


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

Глава 2: Линейные алгоритмы и один проход

Линейный поиск и его ниша

Линейный поиск — фундаментальный алгоритм, основанный на последовательной проверке элементов коллекции. Его детерминированность гарантирует, что при наличии целевого элемента в коллекции он будет обнаружен за конечное число шагов. Алгоритм не требует специальной организации данных и работает с массивами, списками и другими структурами с последовательным доступом.

public class LinearSearch {
public static int find(int[] array, int target) {
for (int i = 0; i < array.length; i++) {
if (array[i] == target) {
return i; // Элемент найден
}
}
return -1; // Элемент отсутствует
}

// Универсальная версия для любых Comparable объектов
public static <T extends Comparable<T>> int findGeneric(T[] array, T target) {
for (int i = 0; i < array.length; i++) {
if (array[i].compareTo(target) == 0) {
return i;
}
}
return -1;
}
}



Анализ сложности

Временная сложность линейного поиска составляет O(n) в худшем и среднем случае, когда элемент отсутствует или находится в конце коллекции. В лучшем случае (элемент первый) сложность равна O(1). Пространственная сложность — O(1), так как алгоритм использует лишь несколько переменных для индексации и сравнения.

Важное свойство линейного поиска — отсутствие накладных расходов на подготовку данных. В отличие от бинарного поиска, требующего отсортированной коллекции, или хеш-таблиц, нуждающихся в вычислении хешей, линейный поиск сразу приступает к решению задачи.


Оптимальные сценарии применения

Маленькие объемы данных (n < 100)

Для коллекций до 100 элементов преимущества алгоритмов с лучшей асимптотикой нивелируются их константными издержками. Рассмотрим практический пример: поиск в массиве из 50 целых чисел.

Линейный поиск выполнит в среднем 25 сравнений. При скорости современных процессоров (миллиарды операций в секунду) это занимает наносекунды. Бинарный поиск потребует предварительной сортировки массива — операция O(n log n), которая для 50 элементов означает примерно 300 операций сравнений и перестановок.

Неотсортированные данные для разовых операций

Когда данные не упорядочены и требуется выполнить единичный поиск, сортировка становится избыточной операцией. Сортировка массива из n элементов требует O(n log n) времени, что для однократного поиска хуже, чем O(n) линейного поиска.

Ситуации с ограниченными ресурсами разработки

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


#Java #для_новичков #beginner #algorithm #line_search
👍3
Количественное сравнение производительности

Рассмотрим конкретные измерения для массива из 50 элементов на процессоре с тактовой частотой 3 ГГц:
Линейный поиск: 25 сравнений × 0.3 наносекунды ≈ 7.5 наносекунд
Бинарный поиск: 6 сравнений × 0.3 нс + накладные расходы на вычисление середины ≈ 5 наносекунд
Подготовка к бинарному поиску: сортировка 50 элементов ≈ 300 операций × 0.3 нс ≈ 90 наносекунд
Разница в 2.5 наносекунды между линейным и бинарным поиском теряется на фоне 90 наносекунд, затраченных на сортировку. Для HashMap ситуация аналогична — вычисление хеша, обработка коллизий и аллокация памяти создают значительные накладные расходы.


Практические реализации в Java

В стандартной библиотеке Java линейный поиск представлен несколькими методами:
// Для List интерфейса
List<String> list = Arrays.asList("A", "B", "C", "D");
int index = list.indexOf("C"); // Линейный поиск

// Для массивов через Collections
String[] array = {"A", "B", "C", "D"};
int pos = Collections.indexOf(Arrays.asList(array), "C");

// Использование Stream API (также линейный поиск)
Optional<String> result = Stream.of(array)
.filter(s -> s.equals("C"))
.findFirst();



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

public static int findInt(int[] array, int target) {
// Развернутый цикл для потенциальной оптимизации JIT-компилятором
int len = array.length;
for (int i = 0; i < len; i++) {
if (array[i] == target) {
return i;
}
}
return -1;
}



Когда переходить к сложным алгоритмам

Критерии для выбора более сложных алгоритмов поиска:
Объем данных превышает 1000 элементов — асимптотические преимущества становятся значимыми
Многократный поиск в одной коллекции — инвестиции в подготовку данных окупаются
Критическая производительность — когда наносекунды имеют значение в высоконагруженных системах
Специфические требования — поиск по диапазону, префиксу или сложному ключу

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

import java.util.*;

public class Task140126 {
public static void main(String[] args) {
List<Double> list = Arrays.asList(1.0, 2.0, 3.0, Double.NaN, 4.0);

int index1 = list.indexOf(Double.NaN);
int index2 = list.lastIndexOf(Double.NaN);
int index3 = list.indexOf(Double.valueOf(Double.NaN));
boolean contains = list.contains(Double.NaN);

System.out.println(index1 + " " + index2 + " " + index3 + " " + contains);
}
}


#Tasks
👍1
Варианты ответа:
Anonymous Quiz
56%
3 3 3 true
13%
3 3 -1 true
31%
-1 -1 -1 false
0%
-1 -1 3 false
👍2
Чем prototype-бин опасен в Spring? 🤓

Ответ:

Spring не управляет жизненным циклом prototype после создания.

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


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


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

Со́фья Васи́льевна Ковале́вская (урождённая Корвин-Круковская; 3 [15] января 1850, Москва — 29 января [10 февраля] 1891, Стокгольм)русский математик и механик, с 1889 года — иностранный член-корреспондент Петербургской академии наук. Первая в мире женщина — профессор математики. Её работы в области дифференциальных уравнений и аналитической механики лежат в основе алгоритмов численного моделирования, которые используются в современном софте для физических и инженерных расчетов.

Дави́д Пи́нхусович Ми́льман (15 января 1912,[3] Чечельник, Ольгопольский уезд, Подольская губерния — 12 июля 1982, Тель-Авив)советский и израильский математик, известный работами в области функционального анализа, в частности теории операторов. Кандидат физико-математических наук (1939). Его теоремы и методы используются в теории оптимизации и машинном обучении (Gradient Descent и другие методы опираются на этот раздел математики).


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

2001 — начал работу англоязычный сайт «Википедии», вики-энциклопедии со свободно распространяемым содержимым.


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

Глава 2: Линейные алгоритмы и один проход

Агрегация за один проход: максимум эффективности

Однопроходные алгоритмы (One-Pass Algorithms) обрабатывают входные данные строго последовательно, без возвратов и повторных чтений. Каждый элемент просматривается ровно один раз, а результаты агрегации обновляются инкрементально. Этот подход идеален для потоковых данных, больших наборов, не помещающихся в память, и систем реального времени.

Основной принцип: поддерживать минимальное необходимое состояние (state) и обновлять его при получении каждого нового элемента. Такой подход требует O(1) дополнительной памяти (кроме самих данных) и выполняется за O(n) времени.


Базовые операции: минимум, максимум, сумма

Поиск минимума и максимума

Простейший однопроходный алгоритм ищет минимальный и максимальный элементы, поддерживая двух кандидатов:
public class ExtremumFinder {
public static MinMax findMinMax(int[] arr) {
int min = arr[0], max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] < min) min = arr[i];
if (arr[i] > max) max = arr[i];
}
return new MinMax(min, max);
}
}

Оптимизация: обработка элементов парами уменьшает количество сравнений с 2n до 3n/2. Вместо сравнения каждого элемента с обоими экстремумами, сначала сравниваем пару элементов между собой, затем меньший — с минимумом, больший — с максимумом.


Сумма и количество

Вычисление суммы — канонический пример свертки (fold):
public long sum(int[] arr) {
long total = 0; // long для избежания переполнения
for (int value : arr) {
total += value;
}
return total;
}


Для подсчета элементов, удовлетворяющих условию, добавляем фильтрацию:
public long countPositive(int[] arr) {
long count = 0;
for (int value : arr) {
if (value > 0) count++;
}
return count;
}



Усложненные задачи агрегации

Поиск двух максимальных значений

Задача: найти две самые старые книги (два минимальных года издания).

Алгоритм поддерживает двух кандидатов:
public static int[] findTwoOldest(int[] years) {
if (years.length < 2) throw new IllegalArgumentException();

// Инициализируем упорядоченно
int oldest = Math.min(years[0], years[1]);
int secondOldest = Math.max(years[0], years[1]);

for (int i = 2; i < years.length; i++) {
if (years[i] < oldest) {
secondOldest = oldest;
oldest = years[i];
} else if (years[i] < secondOldest) {
secondOldest = years[i];
}
}
return new int[]{oldest, secondOldest};
}

Особенность: алгоритм корректно обрабатывает дубликаты и все возможные порядки следования элементов. Сложность — O(n) с ровно n-1 сравнениями в худшем случае.



Вычисление среднего и дисперсии за один проход

Традиционно дисперсию вычисляют в два прохода, но алгоритм Уэлфорда (Welford) решает задачу за один проход, обеспечивая численную стабильность:
public class Statistics {
public static Stats compute(int[] data) {
double mean = 0, m2 = 0; // m2 - сумма квадратов отклонений
int n = 0;

for (int x : data) {
n++;
double delta = x - mean;
mean += delta / n;
m2 += delta * (x - mean);
}

double variance = (n > 1) ? m2 / (n - 1) : 0;
return new Stats(mean, variance);
}
}

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



#Java #для_новичков #beginner #algorithm #line_search
👍4
Поиск наиболее частого элемента

Для поиска моды (наиболее частого элемента) используем хеш-таблицу для подсчета частот:

public static String mostFrequentAuthor(String[] authors) {
Map<String, Integer> freq = new HashMap<>();
String mostFreq = null;
int maxCount = 0;

for (String author : authors) {
int count = freq.getOrDefault(author, 0) + 1;
freq.put(author, count);

if (count > maxCount) {
maxCount = count;
mostFreq = author;
}
}
return mostFreq;
}


Пространственная сложность — O(k), где k — количество уникальных авторов. Если элемент составляет строгое большинство (> n/2), эффективнее алгоритм Бойера-Мура, который находит кандидата за O(n) времени и O(1) памяти:
public static String majorityElement(String[] arr) {
String candidate = null;
int count = 0;

for (String s : arr) {
if (count == 0) {
candidate = s;
count = 1;
} else if (s.equals(candidate)) {
count++;
} else {
count--;
}
}

// Второй проход для проверки (тоже O(n))
return candidate; // если гарантировано наличие большинства
}



Принцип: один сложный проход против нескольких простых

Константные факторы имеют значение

Хотя с точки зрения асимптотической нотации 2*O(n) = O(n), на практике константные множители существенны.

Рассмотрим обработку массива из 10 миллионов элементов:
Два последовательных прохода: 20 миллионов операций чтения, двойные накладные расходы на управление циклом, потенциально двойное количество кэш-промахов.
Один сложный проход: 10 миллионов операций чтения, но больше вычислений на элемент.
Если один проход в 1.3 раза "тяжелее" на элемент, но избавляет от второго прохода, общее время: 1.3T против 2T — выигрыш ~35%.


Использование кэша процессора

Современные процессоры имеют иерархию памяти: кэш L1 (1-2 нс), L2 (3-10 нс), L3 (10-20 нс), основная память (80-100 нс). Однопроходные алгоритмы лучше используют пространственную локальность: данные, прочитанные в кэш, сразу используются для всех необходимых вычислений.

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


Комплексная агрегация за один проход

Многие задачи можно решить за один проход, комбинируя несколько агрегаций:
public class CombinedStats {
int min, max, count;
long sum;

public void process(int value) {
if (value < min) min = value;
if (value > max) max = value;
sum += value;
count++;
}

public double average() {
return (double) sum / count;
}
}
Такой подход особенно ценен при обработке потоковых данных, где невозможно хранить историю.



Практическое применение

Потоковая обработка данных
Однопроходные алгоритмы — основа систем обработки потоков (Apache Kafka, Apache Flink, Apache Storm). Они позволяют вычислять скользящие средние, агрегировать метрики в реальном времени, обнаруживать аномалии без хранения всего потока.

Анализ больших данных
В MapReduce и подобных фреймворках комбинаторы (combiners) используют однопроходные агрегации для уменьшения объема передаваемых данных. Вместо передачи всех значений, каждый узел предварительно агрегирует свою порцию.

Мониторинг и телеметрия
Системы мониторинга (Prometheus, InfluxDB) вычисляют метрики на лету, используя однопроходные алгоритмы для экономии памяти и процессорного времени.


Ограничения и предостережения

Не все задачи решаемы за один проход.

Например:
Поиск медианы требует хранения хотя бы части данных
Вычисление корреляции между двумя потоками требует их синхронизации
Некоторые статистики (квантили) требуют случайного доступа к данным
Однопроходные алгоритмы часто являются приближенными или требуют компромиссов между точностью и эффективностью.

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

import java.util.List;

public class Task150126 {
public static void main(String[] args) {
List<String> data = List.of("A", "B", "C");

long count = data.stream()
.peek(s -> System.out.print(s))
.map(s -> s + s)
.count();

System.out.print(count);
}
}


#Tasks
👍1
Варианты ответа:
Anonymous Quiz
32%
ABC3
11%
AABBCC3
37%
3
21%
Exception
👍1🔥1😱1
Почему DTO важны даже в маленьких проектах? 🤓

Ответ:

DTO изолируют API от доменной модели.

Это упрощает изменения, повышает безопасность и предотвращает утечку внутренней структуры.

Отказ от DTO почти всегда приводит к проблемам при росте проекта.


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


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

Не нашел(


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

1973 — «Луноход-2» отправился в путешествие по поверхности Луны.

1986 — анонс Macintosh Plus. Он был третьей моделью в линейке Macintosh, представленной через два года после оригинального Macintosh и чуть более чем через год после Macintosh 512K , по цене 2599 долларов США. В качестве эволюционного улучшения по сравнению с 512K он поставлялся со стандартным 1 МБ оперативной памяти, расширяемой до 4 МБ, и внешней шиной SCSI , а также с другими небольшими улучшениями. Первоначально корпус компьютера был того же бежевого цвета, что и оригинальный Macintosh, Pantone 453; однако в 1987 году цвет корпуса был изменен на долговечный теплый серый цвет «Платина». Это самая ранняя модель Macintosh, способная запускать системное программное обеспечение 5 , 6 и 7 , вплоть до System 7.5.5, но не System 7.5.2.


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

Глава 2. Линейные алгоритмы и один проход

Практика:
Подсчитать общее количество книг, средний год издания.
Найти самого плодовитого автора.
Задача со звездочкой: Найти двух авторов с наименьшим количеством книг за один проход.

Мы реализуем:
Подсчет общего количества книг.
Вычисление среднего года издания.
Поиск самого плодовитого автора (с наибольшим количеством книг).
Задача со звездочкой: поиск двух авторов с наименьшим количеством книг за один проход.

В конце проведем анализ: почему всё решается за O(n), зачем не нужна сортировка и что изменится при увеличении данных в 1000 раз.


Подготовка к уроку

Убедитесь, что проект готов:
Класс Book с полями title, author, year (геттеры есть).
Класс Library с List<Book> books (ArrayList или CopyOnWriteArrayList из предыдущего урока).
Методы добавления книг и вывода.
Откройте Library.java.
Импорты: Убедитесь, что есть import java.util.HashMap; (для подсчета авторов) и import java.util.Map;.

Реализация подсчета общего количества книг и среднего года

Создайте метод countBooks():
Возвращает int — размер списка книг.
Просто используйте books.size().

Создайте метод calculateAverageYear():
Возвращает double (средний год).
Инициализируйте переменные: int totalYear = 0; int count = 0;
В цикле for-each по books:
totalYear += book.getYear();
count++;

Если count == 0 — верните 0 или бросьте исключение.
Возвращает (double) totalYear / count.

Реализация поиска самого плодовитого автора

Создайте метод findMostProductiveAuthor():
Возвращает String — имя автора с наибольшим количеством книг.
Используйте HashMap<String, Integer> для подсчета: ключ — author, значение — количество книг.

В одном проходе по books:
String author = book.getAuthor();
Если author null — пропустите или обработайте.
map.put(author, map.getOrDefault(author, 0) + 1);

После цикла найдите автора с максимальным значением:
String maxAuthor = null;
int maxCount = 0;
Переберите map.entrySet() — найдите max.

Верните maxAuthor (или "Нет авторов", если пусто).

Задача со звездочкой: Два автора с наименьшим количеством книг за один проход

Создайте метод findTwoLeastProductiveAuthors():
Возвращает массив String[2] или List<String> с двумя авторами (или меньше, если авторов мало).
Используйте HashMap<String, Integer> для подсчета, как выше.
После подсчета найдите двух авторов с минимальным количеством книг.

Варианты:
Перебрать map.entrySet() два раза: сначала найти min, потом второй min.
Или собрать все пары в List<Map.Entry<String, Integer>>, отсортировать (но это O(n log n) — не за один проход).
За один проход: Поддерживайте две переменные min1 и min2 (String и count), обновляйте их при переборе map.
Верните двух авторов (или один, если авторов меньше 2).


#Java #для_новичков #beginner #algorithm #Практика
👍2🔥1