← Назад к вопросам
Происходит ли сортировка в TreeMap по ключу
2.0 Middle🔥 191 комментариев
#Коллекции#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Происходит ли сортировка в TreeMap по ключу?
Да, TreeMap всегда сортирует элементы по ключу. Это её главное отличие от HashMap. Всё основано на красно-чёрном дереве (Red-Black Tree).
Основная характеристика TreeMap
// HashMap — БЕЗ сортировки
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("zebra", 3);
hashMap.put("apple", 1);
hashMap.put("banana", 2);
for (String key : hashMap.keySet()) {
System.out.println(key);
}
// Вывод: zebra, apple, banana (случайный порядок)
// TreeMap — СОРТИРУЕТ по ключам
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("zebra", 3);
treeMap.put("apple", 1);
treeMap.put("banana", 2);
for (String key : treeMap.keySet()) {
System.out.println(key);
}
// Вывод: apple, banana, zebra (ОТСОРТИРОВАНО!)
Как работает сортировка
1. По умолчанию: Natural Order (Comparable)
TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("Charlie", 30);
treeMap.put("Alice", 25);
treeMap.put("Bob", 28);
for (String key : treeMap.keySet()) {
System.out.println(key); // Alice, Bob, Charlie
}
Строки сортируются лексикографически (как в словаре):
- "Alice" < "Bob" < "Charlie" (по букве A < B < C)
TreeMap<Integer, String> numbers = new TreeMap<>();
numbers.put(5, "five");
numbers.put(2, "two");
numbers.put(8, "eight");
numbers.put(1, "one");
for (Integer key : numbers.keySet()) {
System.out.println(key); // 1, 2, 5, 8 (по возрастанию)
}
2. Пользовательская сортировка: Comparator
// Сортировка по убыванию
TreeMap<Integer, String> descending = new TreeMap<>(
(a, b) -> b.compareTo(a) // Reverse order
);
descending.put(5, "five");
descending.put(2, "two");
descending.put(8, "eight");
for (Integer key : descending.keySet()) {
System.out.println(key); // 8, 5, 2 (по убыванию!)
}
// Сортировка по длине строки
TreeMap<String, Integer> byLength = new TreeMap<>(
(s1, s2) -> {
int cmp = Integer.compare(s1.length(), s2.length());
if (cmp == 0) {
return s1.compareTo(s2); // Если длина одинакова
}
return cmp;
}
);
byLength.put("apple", 1); // длина 5
byLength.put("go", 2); // длина 2
byLength.put("programming", 3); // длина 11
for (String key : byLength.keySet()) {
System.out.println(key); // go, apple, programming
}
Внутренняя структура: Red-Black Tree
TreeMap использует красно-чёрное дерево:
Этапы вставки: put(5), put(3), put(7), put(1), put(9)
5 (root, black)
/ \
3 7
/ \ / \
1 4 6 9
Свойства:
1. Каждый узел красный или чёрный
2. Корень всегда чёрный
3. Листья чёрные (NULL)
4. Красный узел имеет только чёрных детей
5. Все пути от узла до листьев содержат одинаковое число чёрных узлов
Это гарантирует:
✅ Сбалансированное дерево
✅ Поиск O(log n)
✅ Вставка O(log n)
✅ Удаление O(log n)
Сложность операций TreeMap vs HashMap
| Операция | TreeMap | HashMap | LinkedHashMap |
|---|---|---|---|
| get(key) | O(log n) | O(1) | O(1) |
| put(key, value) | O(log n) | O(1) | O(1) |
| remove(key) | O(log n) | O(1) | O(1) |
| containsKey(key) | O(log n) | O(1) | O(1) |
| Порядок сохранён | ✅ Да | ❌ Нет | ✅ insertion order |
| Память | +overhead | меньше | меньше |
Вывод: TreeMap медленнее в поиске, но гарантирует порядок.
Практические примеры
Пример 1: Отсортированный лидерборд
NavigableMap<Integer, List<String>> leaderboard = new TreeMap<>(
(a, b) -> b.compareTo(a) // По убыванию score
);
leaderboard.put(100, Arrays.asList("Alice", "Bob"));
leaderboard.put(95, Arrays.asList("Charlie"));
leaderboard.put(88, Arrays.asList("David"));
// Вывод топ 3
int rank = 1;
for (Map.Entry<Integer, List<String>> entry : leaderboard.entrySet()) {
System.out.println(rank + ". Score: " + entry.getKey() + ": " + entry.getValue());
rank++;
if (rank > 3) break;
}
// Output:
// 1. Score: 100: [Alice, Bob]
// 2. Score: 95: [Charlie]
// 3. Score: 88: [David]
Пример 2: Интервалы (Range Queries)
NavigableMap<Integer, String> events = new TreeMap<>();
events.put(10, "Event A");
events.put(20, "Event B");
events.put(30, "Event C");
events.put(40, "Event D");
events.put(50, "Event E");
// Получить все события между 20 и 40
NavigableMap<Integer, String> range = events.subMap(20, true, 40, true);
for (Map.Entry<Integer, String> entry : range.entrySet()) {
System.out.println(entry); // 20=Event B, 30=Event C, 40=Event D
}
// Получить события до 30
NavigableMap<Integer, String> before = events.headMap(30, true);
for (Map.Entry<Integer, String> entry : before.entrySet()) {
System.out.println(entry); // 10=Event A, 20=Event B, 30=Event C
}
// Получить события после 30
NavigableMap<Integer, String> after = events.tailMap(30, false);
for (Map.Entry<Integer, String> entry : after.entrySet()) {
System.out.println(entry); // 40=Event D, 50=Event E
}
// Первый элемент
Map.Entry<Integer, String> first = events.firstEntry();
System.out.println(first); // 10=Event A
// Последний элемент
Map.Entry<Integer, String> last = events.lastEntry();
System.out.println(last); // 50=Event E
// Меньший элемент (ceiling)
Map.Entry<Integer, String> ceiling = events.ceilingEntry(25);
System.out.println(ceiling); // 30=Event C (первый >= 25)
// Больший элемент (floor)
Map.Entry<Integer, String> floor = events.floorEntry(25);
System.out.println(floor); // 20=Event B (последний <= 25)
Пример 3: Собственный Comparator для объектов
public class Student {
private String name;
private int gpa; // Grade Point Average
public Student(String name, int gpa) {
this.name = name;
this.gpa = gpa;
}
public String getName() { return name; }
public int getGpa() { return gpa; }
@Override
public String toString() {
return name + " (GPA: " + gpa + ")";
}
}
// Сортировка студентов по GPA
TreeMap<Student, String> students = new TreeMap<>(
(s1, s2) -> {
// По убыванию GPA
int cmp = Integer.compare(s2.getGpa(), s1.getGpa());
if (cmp == 0) {
return s1.getName().compareTo(s2.getName()); // Если GPA одинаков
}
return cmp;
}
);
students.put(new Student("Alice", 95), "Engineering");
students.put(new Student("Bob", 88), "Engineering");
students.put(new Student("Charlie", 95), "Science");
for (Map.Entry<Student, String> entry : students.entrySet()) {
System.out.println(entry.getKey() + " - " + entry.getValue());
}
// Output:
// Alice (GPA: 95) - Engineering
// Charlie (GPA: 95) - Science
// Bob (GPA: 88) - Engineering
Пример 4: Временные события (Timeline)
import java.time.LocalDateTime;
NavigableMap<LocalDateTime, String> timeline = new TreeMap<>();
timeline.put(LocalDateTime.of(2024, 1, 15, 9, 30), "Meeting with client");
timeline.put(LocalDateTime.of(2024, 1, 10, 14, 0), "Code review");
timeline.put(LocalDateTime.of(2024, 1, 20, 16, 45), "Deployment");
timeline.put(LocalDateTime.of(2024, 1, 5, 10, 15), "Project kickoff");
// Все события отсортированы по времени
for (Map.Entry<LocalDateTime, String> entry : timeline.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// Output:
// 2024-01-05T10:15: Project kickoff
// 2024-01-10T14:00: Code review
// 2024-01-15T09:30: Meeting with client
// 2024-01-20T16:45: Deployment
// События за период
LocalDateTime start = LocalDateTime.of(2024, 1, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2024, 1, 18, 0, 0);
NavigableMap<LocalDateTime, String> period = timeline.subMap(start, end);
for (Map.Entry<LocalDateTime, String> entry : period.entrySet()) {
System.out.println(entry.getValue());
}
// Output:
// Code review
// Meeting with client
Когда использовать TreeMap?
✅ Используй TreeMap когда:
- Нужна отсортированная коллекция по ключам
- Нужны range queries (между значениями)
- Нужны first/last элементы
- Нужна NavigableMap функциональность
- Нужна предсказуемость порядка
❌ НЕ используй TreeMap когда:
- Нужна максимальная скорость (используй HashMap)
- Не важен порядок элементов (используй HashMap)
- Нужен insertion order (используй LinkedHashMap)
- Много операций поиска (HashMap быстрее)
Сравнение трёх map'ов
Map<String, Integer> data = new LinkedHashMap<>();
data.put("zebra", 1);
data.put("apple", 2);
data.put("banana", 3);
// HashMap
Map<String, Integer> hashMap = new HashMap<>(data);
for (String key : hashMap.keySet()) {
System.out.println(key); // apple, zebra, banana (случайный)
}
// TreeMap
Map<String, Integer> treeMap = new TreeMap<>(data);
for (String key : treeMap.keySet()) {
System.out.println(key); // apple, banana, zebra (ОТСОРТИРОВАНО)
}
// LinkedHashMap
Map<String, Integer> linkedHashMap = new LinkedHashMap<>(data);
for (String key : linkedHashMap.keySet()) {
System.out.println(key); // zebra, apple, banana (insertion order)
}
Итоговый вывод
TreeMap — это отсортированная Map, основанная на красно-чёрном дереве.
Ключевые моменты:
- Всегда сортирует по ключам (естественный или пользовательский порядок)
- Сложность O(log n) для основных операций
- NavigableMap методы (subMap, headMap, tailMap, ceiling, floor)
- Red-Black Tree гарантирует баланс
- Медленнее HashMap, но с гарантированным порядком
Треди для данных, где порядок важен: рейтинги, временные шкалы, интервалы!