
Структура TreeSet: красно-черное дерево
Как мы упоминали ранее, структура класса TreeSet в Java реализована как самобалансирующееся бинарное дерево поиска, известное как красно-черное дерево. Давайте объясним, что это такое... Красно-черное дерево представляет собой сбалансированную структуру данных бинарного поиска, обычно используемую для хранения и организации отсортированных данных. Оно получило свое название благодаря свойствам, которые поддерживают его баланс:- Каждый узел дерева является либо красным, либо черным.
- Корень красно-черного дерева всегда черный.
- Все листовые узлы (NIL-узлы или нулевые ссылки) являются черными.
- Если узел дерева красный, то его дочерние узлы черные.
- Каждый простой путь от узла к его потомкам содержит одинаковое количество черных узлов.
Особенности и недостатки TreeSet
TreeSet предоставляет несколько ключевых функций, которые делают его ценным инструментом для управления коллекциями объектов в отсортированном виде. Преимущества:- Автоматическое поддержание элементов в отсортированном порядке. При добавлении или удалении элементов древовидная структура корректируется, чтобы сохранить их отсортированными. Это устраняет необходимость ручной сортировки и обеспечивает эффективный доступ в порядке возрастания или убывания.
- Эффективные операции поиска, вставки и удаления. Использует структуру бинарного дерева поиска, что позволяет выполнять эти операции с временной сложностью O(log N). Где N — количество элементов.
- TreeSet гарантирует уникальность элементов, используя их естественный порядок или пользовательский Comparator. Это полезно при работе с коллекциями, требующими различных элементов.
- Немного медленнее, чем HashSet из-за поддержания отсортированного порядка.
- Не допускает null-элементы, так как полагается на естественный порядок или пользовательский Comparator для сравнения элементов.
Java TreeSet: примеры основных операций
Теперь давайте продемонстрируем, как создать TreeSet в Java, получить размер коллекции, добавить в нее элементы, удалить их и проверить, присутствует ли элемент в TreeSet. Эти примеры кода демонстрируют основные операции при работе с TreeSet: создание экземпляра, добавление элементов, удаление элементов, проверка наличия элемента и получение размера TreeSet. Создание экземпляра TreeSet и добавление элементов:
// Создание TreeSet типа Integer
TreeSet<Integer> numbers = new TreeSet<>();
// Добавление элементов в TreeSet
numbers.add(18);
numbers.add(2);
numbers.add(7);
numbers.add(28);
System.out.println(numbers); // Вывод: [2, 7, 18, 28]
Здесь мы используем метод add() для добавления 4 чисел в наш TreeSet. Если создать метод main и запустить программу, вывод будет в отсортированном порядке (2, 7, 18, 28).
Удаление элементов из TreeSet:
TreeSet<String> names = new TreeSet<>();
names.add("Johnny");
names.add("Ivy");
names.add("David");
names.add("Ricky");
// Удаление элемента из TreeSet
names.remove("David");
System.out.println(names); // Вывод: [Ivy, Johnny, Ricky]
Проверка наличия элемента в TreeSet:
TreeSet<String> musicalInstrument = new TreeSet<>();
musicalInstrument.add("Piano");
musicalInstrument.add("Drums");
musicalInstrumentfruits.add("Violin");
musicalInstrument.add("Double Bass");
// Проверка существования элемента в TreeSet
boolean containsPiano = musicalInstrument.contains("Piano");
boolean containsCello = musicalInstrument.contains("Cello");
System.out.println(containsPiano); // Вывод: true
System.out.println(containsCello); // Вывод: false
Получение размера TreeSet:
TreeSet<Character> letters = new TreeSet<>();
letters.add('A');
letters.add('B');
letters.add('C');
letters.add('D');
// Получение размера TreeSet
int size = letters.size();
System.out.println(size); // Вывод: 4
Сортировка и итерирование в TreeSet
TreeSet в Java предоставляет мощные функции для сортировки и итерирования по коллекциям элементов. В этом разделе мы рассмотрим различные техники сортировки элементов, итерирования по TreeSet и выполнения диапазонного поиска с использованием методов subSet(), headSet() и tailSet(). По умолчанию TreeSet автоматически поддерживает элементы в отсортированном порядке. Однако мы можем настроить поведение сортировки, предоставив Comparator при создании TreeSet. Давайте посмотрим на пример:
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetSortingExample {
public static void main(String[] args) {
// Создание TreeSet с пользовательской сортировкой
TreeSet<Integer> numbers = new TreeSet<>(Comparator.reverseOrder());
// Добавление элементов в TreeSet
numbers.add(5);
numbers.add(3);
numbers.add(7);
numbers.add(1);
System.out.println(numbers); // Вывод: [7, 5, 3, 1]
}
}
В приведенном выше коде мы создаем TreeSet типа Integer и предоставляем пользовательский Comparator, используя Comparator.reverseOrder() для сортировки элементов в порядке убывания. Полученный TreeSet будет содержать элементы [7, 5, 3, 1].
Существует несколько способов итерирования по TreeSet. Мы можем использовать итератор или использовать расширенный цикл for-each. Давайте посмотрим примеры обоих подходов:
import java.util.TreeSet;
import java.util.Iterator;
public class TreeSetIterationExample {
public static void main(String[] args) {
TreeSet<String> names = new TreeSet<>();
names.add("Johnny");
names.add("Ivy");
names.add("Ricky");
names.add("David");
// Итерирование с использованием итератора
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
System.out.println(name);
}
// Итерирование с использованием расширенного цикла for-each
for (String name : names) {
System.out.println(name);
}
}
}
В приведенном выше коде мы создаем TreeSet с именем names и добавляем несколько элементов. Затем мы демонстрируем два способа итерирования по TreeSet: используя итератор и используя расширенный цикл for-each.
TreeSet предоставляет методы для выполнения диапазонного поиска, позволяющие извлекать подмножества элементов на основе определенных критериев. Три основных метода для диапазонного поиска:- subSet(E fromElement, E toElement): Возвращает подмножество элементов от fromElement (включительно) до toElement (исключительно).
- headSet(E toElement): Возвращает подмножество элементов меньше toElement.
- tailSet(E fromElement): Возвращает подмножество элементов больше или равных fromElement.
import java.util.TreeSet;
public class TreeSetRangeSearchExample {
public static void main(String[] args) {
TreeSet<Integer> numbers = new TreeSet<>();
numbers.add(1);
numbers.add(3);
numbers.add(5);
numbers.add(7);
numbers.add(9);
// Выполнение диапазонного поиска с использованием subSet()
TreeSet<Integer> subset = new TreeSet<>(numbers.subSet(3, 8));
System.out.println(subset); // Вывод: [3, 5, 7]
// Выполнение диапазонного поиска с использованием headSet()
TreeSet<Integer> headSet = new TreeSet<>(numbers.headSet(6));
System.out.println(headSet); // Вывод: [1, 3, 5]
// Выполнение диапазонного поиска с использованием tailSet()
TreeSet<Integer> tailSet = new TreeSet<>(numbers.tailSet(5));
System.out.println(tailSet); // Вывод: [5, 7, 9]
}
}
В приведенном выше коде мы создаем TreeSet с именем numbers и добавляем несколько элементов. Затем мы демонстрируем диапазонный поиск с использованием методов subSet(), headSet() и tailSet().- subset TreeSet содержит элементы между 3 (включительно) и 8 (исключительно), которые являются [3, 5, 7].
- headSet TreeSet содержит элементы меньше 6, которые являются [1, 3, 5].
- tailSet TreeSet содержит элементы больше или равные 5, которые являются [5, 7, 9].
Методы для навигации
Вы изучили основы использования TreeSet для хранения и извлечения элементов в отсортированном порядке. Но знаете ли вы, что TreeSet имеет очень удобные методы навигации для быстрого поиска элементов без итерирования по всему набору? Давайте погрузимся в эти методы и увидим их в действии.
Получение первого и последнего элементов
Метод first() возвращает наименьший (или «самый низкий») элемент в TreeSet, в то время как last() возвращает наибольший (или «самый высокий»). Идеально подходит, когда вы хотите мгновенно получить граничные элементы.
import java.util.TreeSet;
public class TreeSetFirstLastExample {
public static void main(String[] args) {
TreeSet numbers = new TreeSet<>();
numbers.add(10);
numbers.add(40);
numbers.add(20);
numbers.add(30);
System.out.println(\"TreeSet: \" + numbers);
System.out.println(\"Первый элемент: \" + numbers.first());
System.out.println(\"Последний элемент: \" + numbers.last());
}
}
first() и last() являются операциями O(log n), так как TreeSet основан на древовидной структуре. Это все еще довольно эффективно для извлечения граничных элементов.
Поиск элементов с помощью ceiling(), floor(), higher() и lower()
Когда вам нужно найти ближайшее совпадение с заданным значением, эти методы спасают ситуацию:
ceiling(e) возвращает наименьший элемент, больший или равный e.
floor(e) возвращает наибольший элемент, меньший или равный e.
higher(e) возвращает наименьший элемент, строго больший e.
lower(e) возвращает наибольший элемент, строго меньший e.
import java.util.TreeSet;
public class TreeSetNavigationExample {
public static void main(String[] args) {
TreeSet numbers = new TreeSet<>();
numbers.add(15);
numbers.add(10);
numbers.add(25);
numbers.add(20);
System.out.println(\"TreeSet: \" + numbers);
System.out.println(\"ceiling(18): \" + numbers.ceiling(18)); // Возвращает 20
System.out.println(\"floor(18): \" + numbers.floor(18)); // Возвращает 15
System.out.println(\"higher(20): \" + numbers.higher(20)); // Возвращает 25
System.out.println(\"lower(15): \" + numbers.lower(15)); // Возвращает 10
}
}
Эти методы очень полезны для быстрого поиска, например, нахождения следующего доступного временного интервала в расписании или извлечения ближайшей по цене позиции в отсортированном списке цен.
Удаление элементов с помощью pollFirst() и pollLast()
Нужно немедленно удалить наименьший или наибольший элемент? pollFirst() удаляет и возвращает первый элемент, в то время как pollLast() делает то же самое для последнего элемента.
import java.util.TreeSet;
public class TreeSetPollExample {
public static void main(String[] args) {
TreeSet numbers = new TreeSet<>();
numbers.add(5);
numbers.add(15);
numbers.add(25);
System.out.println(\"TreeSet перед извлечением: \" + numbers);
int first = numbers.pollFirst(); // Удаляет 5
System.out.println(\"Извлечен первый: \" + first);
System.out.println(\"TreeSet теперь: \" + numbers);
int last = numbers.pollLast(); // Удаляет 25
System.out.println(\"Извлечен последний: \" + last);
System.out.println(\"TreeSet теперь: \" + numbers);
}
}
Эти методы пригодятся для сценариев, когда вы хотите поддерживать отсортированную коллекцию, но также удалять граничные элементы по требованию, например, при имитации min-heap или max-heap.
Comparator в TreeSet: сортировка с пользовательскими критериями
Помимо естественного порядка, вы можете использовать пользовательский Comparator для сортировки TreeSet. В этом разделе мы углубимся в концепцию компаратора и его роль в TreeSet. Мы рассмотрим, как создать TreeSet с пользовательским компаратором, и предоставим примеры кода для демонстрации сортировки TreeSet на основе конкретных критериев.Что такое Comparator?
Comparator — это интерфейс в Java, который позволяет выполнять пользовательскую сортировку объектов. Он предоставляет способ определения порядка элементов на основе определенных атрибутов или свойств. Реализуя интерфейс Comparator и переопределяя его метод compare(), мы можем указать, как элементы должны быть отсортированы в TreeSet.Создание TreeSet с пользовательским Comparator
Чтобы создать TreeSet с пользовательским компаратором, нам нужно передать компаратор в качестве аргумента при создании экземпляра TreeSet. Давайте посмотрим на пример:
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetComparatorExample {
public static void main(String[] args) {
// Создание TreeSet с пользовательским компаратором
TreeSet<String> names = new TreeSet<>(new LengthComparator());
// Добавление элементов в TreeSet
names.add("Johnny");
names.add("Ivy");
names.add("Rick");
names.add("David");
System.out.println(names); // Вывод: [Ivy, Johnny, David, Rick]
}
}
class LengthComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
}
В приведенном выше коде мы создаем TreeSet с именем names с пользовательским компаратором LengthComparator. LengthComparator сравнивает длину двух строк и сортирует их соответственно. В результате TreeSet сортируется по длине строк, что дает нам вывод [Ivy, Rick, David, Johnny].
Примеры сортировки TreeSet с использованием Comparator
Помимо создания TreeSet с пользовательским компаратором, мы также можем использовать компаратор для временной сортировки TreeSet без изменения его естественного порядка. Давайте посмотрим на пример:
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetTest {
public static void main(String[] args) {
TreeSet<String> names = new TreeSet<>();
names.add("Johnny");
names.add("Ivy");
names.add("Ricky");
names.add("David");
// Временная сортировка TreeSet с использованием компаратора
TreeSet<String> sortedNames = new TreeSet<>(Comparator.reverseOrder());
sortedNames.addAll(names);
System.out.println(sortedNames); // Вывод: [Ricky, Johnny, Ivy, David]
System.out.println(names); // Вывод: [David, Ivy, Johnny, Ricky]
}
}
В приведенном выше коде мы создаем TreeSet с именем names и добавляем несколько элементов. Затем мы создаем новый TreeSet с именем sortedNames с пользовательским компаратором Comparator.reverseOrder(). Добавляя все элементы из исходного TreeSet names в sortedNames, мы получаем временную отсортированную версию TreeSet.
Сравнение TreeSet с другими структурами данных
Сравнение TreeSet с HashSet
Как TreeSet, так и HashSet являются реализациями интерфейса Set в Java. Однако между ними есть существенные различия. TreeSet: TreeSet хранит уникальные элементы в отсортированном порядке. Он использует самобалансирующееся бинарное дерево поиска (обычно красно-черное дерево) для поддержания отсортированного порядка, что приводит к логарифмической временной сложности для операций, таких как вставка, удаление и поиск. TreeSet эффективен для поддержания отсортированной коллекции и выполнения диапазонных операций. Однако он имеет более высокие затраты памяти из-за древовидной структуры и не допускает null-элементы. HashSet: HashSet хранит уникальные элементы в неупорядоченном виде, используя хеш-коды и хеш-таблицу внутри. Он обеспечивает константную временную сложность для базовых операций, таких как вставка, удаление и поиск. HashSet эффективен для быстрого поиска элементов и не накладывает дополнительных затрат памяти на поддержание порядка. Однако он не гарантирует какого-либо конкретного порядка элементов.Когда использовать TreeSet:
- Когда вам нужно автоматически поддерживать элементы в отсортированном порядке.
- Когда требуются диапазонные операции или нужно эффективно находить элементы в определенном диапазоне.
- Когда дублирующиеся элементы не допускаются и уникальность важна.
- Когда вы готовы пожертвовать немного большим использованием памяти ради преимуществ автоматической сортировки и диапазонных операций.
Сравнение TreeSet с ArrayList
ArrayList — это реализация динамического массива в Java. Вот ключевые различия между TreeSet и ArrayList:- TreeSet: TreeSet хранит уникальные элементы в отсортированном порядке, обеспечивая эффективные операции для поддержания отсортированной коллекции и выполнения диапазонного поиска. Имеет логарифмическую временную сложность для операций. TreeSet не подходит для произвольного доступа или поддержания порядка вставки.
- ArrayList: ArrayList хранит элементы в порядке вставки, обеспечивая быстрый произвольный доступ к элементам с использованием индексов. Имеет константную временную сложность для большинства операций при доступе к элементам по индексу. Однако имеет линейную временную сложность для вставки или удаления элементов из середины списка, так как это требует смещения элементов.
Когда рассматривать другие структуры данных
- Если поддержание элементов в отсортированном порядке не требуется, и вам нужен быстрый поиск элементов или неупорядоченная коллекция, HashSet может быть лучшим выбором.
- Если вам требуется частый произвольный доступ к элементам по индексу или необходимо поддерживать порядок вставки, ArrayList будет более подходящим.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ