Java中的TreeSet
TreeSet是Java中SortedSet接口最重要的实现之一,它使用树来存储。元素的排序是通过使用它们的自然排序来维护的,无论是否提供了显式比较器。如果要正确实现Set接口,则必须与equals保持一致。
根据所使用的构造函数,它也可以通过在设置创建时提供比较器进行排序。TreeSet通过继承AbstractSet类实现NavigableSet接口。
从上面的图像中可以清楚地看出,导航集扩展了排序集接口。由于集合不保留插入顺序,因此导航集接口提供了在集合中导航的实现方式。实现导航集的类是TreeSet,它是一种自平衡树的实现。因此,此接口为我们提供了通过此树导航的方法。
注意:
- 如果对应的类实现了 Comparable接口 ,则对象才能被称为可比较的。
- String、StringBuffer类和所有包装器类已经实现了Comparable接口,因此我们不会得到ClassCastException异常。 但是,如果我们创建的是用户定义的类或任何不实现可比较接口的Java类的TreeSet,我们将得到ClassCastException异常。为了解决这个问题,我们可以在用户定义的类中实现Comparable,或在创建集合时将Comparator对象传递给构造函数。
- 对于一个空的TreeSet,在尝试将null插入为第一个值时,会从JDK 7中得到NPE。从JDK 7开始,TreeSet根本不接受null。但是,在JDK 6之前,null被接受为第一个值,但任何更多的空值插入TreeSet都会导致NullPointerException。因此,它被认为是一个bug,并在JDK 7中被删除了。
- TreeSet是存储大量经过排序的信息并应快速访问的优秀选择,因为它具有更快的访问和检索时间。
- 将null值插入TreeSet会引发NullPointerException,因为在插入null时,它会与现有元素进行比较,而null不能与任何值进行比较。
TreeSet如何在内部工作?
TreeSet基本上是自平衡二叉搜索树的一种实现,例如红黑树。因此,添加、删除和搜索等操作需要O(log(N))时间。原因是在自平衡树中,确保树的高度在所有操作中始终为O(log(N))。因此,这被认为是存储巨大排序数据并对其执行操作的最有效的数据结构之一。但是,像以排序顺序打印N个元素这样的操作需要O(N)时间。
现在让我们先讨论同步的TreeSet TreeSet的实现不是同步的。这意味着如果多个线程同时访问一个树集,并且其中至少一个线程修改了该集合,则必须在外部进行同步。通常这是通过同步一些自然封装了集合的对象来实现的。如果不存在这样的对象,则应使用Collections.synchronizedSortedSet方法“包装”集合。最好在创建时完成此操作,以防止意外未同步访问集合。可以如下所示实现:
TreeSet类的构造函数如下:
为了创建一个TreeSet,我们需要创建一个TreeSet类的对象。TreeSet类包含各种构造函数,允许可能创建TreeSet。以下是此类中可用的构造函数:
- TreeSet(): 此构造函数用于构建一个空的TreeSet对象,其中元素将以默认自然排序顺序存储。
语法: 如果我们希望创建一个名为ts的空TreeSet,则可以创建如下:
- TreeSet(Comparator): 此构造函数用于构建一个空的TreeSet对象,其中元素需要外部指定排序顺序。
语法: 如果我们希望创建一个名为ts的空TreeSet,并具有外部排序现象,则可以创建如下:
- TreeSet(Collection): 此构造函数用于构建包含给定集合中所有元素的TreeSet对象,其中元素将以默认自然排序顺序存储。简而言之,此构造函数用于将任何Collection对象转换为TreeSet对象时使用。
语法: 如果我们希望创建具有名称ts的TreeSet,则可以创建如下:
- TreeSet(SortedSet): 此构造函数用于构建包含给定sortedset中所有元素的TreeSet对象,其中元素将以默认自然排序顺序存储。简而言之,此构造函数用于将SortedSet对象转换为TreeSet对象。
语法: 如果我们希望创建具有名称ts的TreeSet,则可以创建如下:
TreeSet类中的方法如下所示: 在表格格式中展示,后面将实现展示在实现部分中。 TreeSet实现了SortedSet,因此具有Collection、Set和SortedSet接口中所有方法的可用性。以下是Treeset接口中的方法。在下表中,“?”表示该方法适用于任何类型的对象,包括用户定义的对象。
方法 | 描述 |
---|---|
add(Object o) | 根据创建TreeSet时给定的排序顺序,将指定元素添加到集合中。重复的元素不会被添加。 |
addAll(Collection c) | 将指定Collection中所有元素添加到集合中。Collection中的元素应该是同类型的,否则会抛出ClassCastException异常。重复的元素将不会被添加到TreeSet中。 |
ceiling?(E e) | 返回集合中大于或等于指定元素的最小元素,如果该元素不存在则返回null。 |
clear() | 删除集合中的所有元素。 |
clone() | 返回集合的浅层副本。 |
Comparator comparator() | 返回用于对TreeSet进行排序的比较器,如果使用默认的自然排序,则返回null。 |
contains(Object o) | 如果指定元素在TreeSet中存在,则返回true,否则返回false。 |
descendingIterator?() | 返回一个迭代器,该迭代器以降序遍历集合中的元素。 |
descendingSet?() | 返回TreeSet中包含元素的逆序视图。 |
first() | 如果TreeSet不为空,则返回TreeSet中的第一个元素,否则抛出NoSuchElementException异常。 |
floor?(E e) | 返回集合中小于或等于指定元素的最大元素,如果该元素不存在则返回null。 |
headSet(Object toElement) | 返回小于指定元素的元素组成的TreeSet子集。 |
higher?(E e) | 返回严格大于指定元素的最小元素,如果该元素不存在,则返回null。 |
isEmpty() | 如果该集合为空,则返回true,否则返回false。 |
Iterator iterator() | 返回用于迭代集合元素的迭代器。 |
last() | 如果TreeSet不为空,则返回TreeSet中的最后一个元素,否则抛出NoSuchElementException异常。 |
lower?(E e) | 返回严格小于指定元素的最大元素,如果该元素不存在,则返回null。 |
pollFirst?() | 获取并删除集合中的第一个(最低)元素,如果集合为空,则返回null。 |
pollLast?() | 获取并删除集合中的最后一个(最高)元素,如果集合为空,则返回null。 |
remove(Object o) | 从集合中删除指定元素。 |
size() | 返回集合的大小或集合中元素的数量。 |
spliterator() | 以后期绑定和快速失败机制遍历集合元素并创建Spliterator。 |
subSet(Object fromElement, Object toElement) | 此方法将返回从fromElement到toElement的元素。fromElement是包含的,而toElement是不包含的。 | |
— | — |
tailSet(Object fromElement) | 此方法将返回TreeSet中大于或等于指定元素的元素。 |
Illustration: 下面的实现演示了如何创建和使用TreeSet。
输出:
Implementation:
在这里,我们将对TreeSet对象执行各种操作,以熟悉TreeSet在Java中的方法和概念。让我们看看如何在TreeSet上执行几个经常使用的操作。它们列在下面:
- 添加元素
- 访问元素
- 删除元素
- 遍历元素
现在让我们逐个讨论每个操作,同时借助简洁的Java程序进行理解。
Operation 1: 添加元素
为了向TreeSet添加元素,我们可以使用add()方法。然而,在TreeSet中不保留插入顺序。在内部,对于每个元素,比较并按升序排序。我们需要注意,不允许重复元素,并且所有重复元素都被忽略。并且,TreeSet不接受空值。
示例
输出:
Operation 2: 访问元素
添加元素后,如果我们希望访问元素,我们可以使用类似contains(),first(),last()等内置方法。
示例
输出:
操作3: 删除值
可以使用remove()方法从TreeSet中删除值。有多种其他方法可用于删除第一个值或最后一个值。
示例
输出:
操作4: 通过TreeSet迭代
有多种迭代TreeSet的方式。其中最著名的是使用增强的for循环。而且在练习使用TreeSet解决树、映射和图形问题时,大多数人都会使用这种方法迭代元素。
示例
输出:
TreeSet的特点:
- TreeSet实现了 SortedSet 接口。所以不允许重复的值。
- TreeSet中的对象按排序和升序存储。
- TreeSet不保留元素的插入顺序,但按键排序元素。
- 如果我们依赖默认的自然排序顺序,则插入到树中的对象应该是同类的可比较的。TreeSet不允许插入异构对象。如果我们尝试添加异构对象,它将在运行时抛出 ClassCastException。
- TreeSet只能接受可比较的泛型类型。
例如,StringBuffer类实现Comparable接口
输出