CodeGym/Java 博客/随机的/Java 中的树图
John Squirrels
第 41 级
San Francisco

Java 中的树图

已在 随机的 群组中发布
个会员
如果您正在阅读本文,您很可能熟悉 Map 接口以及可以适当应用的位置。如果没有,那就来这里。今天我们将讨论 Java TreeMap 实现的特点,更具体地说,它与 HashMap 有何不同以及如何正确使用它。

比较 TreeMap、HashMap 和 LinkedHashMap

Map 接口最常用的实现是 HashMap。它易于使用并保证快速访问数据,因此它是解决大多数问题的最佳选择。大多数,但不是全部。有时您需要以结构化方式存储数据并能够在其中导航。在这种情况下,Map 接口的另一个实现 (TreeMap) 可以派上用场。TreeMap 实现了NavigableMap接口,它继承了SortedMap,而 SortedMap 又继承了 Map 接口。 TreeMap 的特点 - 2通过实现NavigableMapSortedMap接口,TreeMap 获得了 HashMap 所没有的额外功能,但它在性能方面付出了代价。还有LinkedHashMapclass ,它还允许您以特定顺序存储数据(将数据添加到地图的顺序)。要了解这三个类之间的差异,请查看此表:
哈希表 链表 树图
数据排序 随机的。不能保证订单会随着时间的推移而维持。 按照添加数据的顺序 按升序或基于指定的比较器
时间复杂度 O(1) O(1) O(日志(n))
实现接口 地图 地图 NavigableMap
SortedMap
地图
数据结构 水桶 水桶 红黑树
支持空键吗? 是的 是的 是的,如果您使用比较器,则允许 null
线程安全?
如您所见,这些类有很多共同点,但也有一些不同之处。虽然 TreeMap 类是最通用的,但它不能总是将null存储为键。此外,访问 TreeMap 的元素花费的时间最长。因此,如果您不需要按某种排序顺序存储数据,最好使用HashMapLinkedHashMap

红黑树

您可能注意到,在幕后,TreeMap 使用一种称为红黑树的数据结构。以这种结构存储数据正是提供数据排序的原因。那么这是什么树呢?让我们弄清楚!想象一下,您需要存储数字-字符串对。数字 16、20、52、55、61、65、71、76、81、85、90、93 和 101 将成为密钥。如果将数据存储在传统列表中,并且需要查找键为 101 的元素,则需要逐步遍历所有 13 个元素才能找到它。对于 13 个元素,这没什么大不了的,但是当处理一百万个元素时,我们就会遇到大问题。为了解决这些问题,程序员使用稍微复杂一点的数据结构。这就是红黑树出场的地方!TreeMap 的特点 - 3搜索元素从树的根开始,在我们的例子中是 61。然后我们将节点值与我们要查找的值进行比较。如果我们的价值较小,那么我们就往左走;如果它更大,那么我们就往右走。重复此过程,直到我们找到所需的值或遇到值为null的元素(树的叶子)。红色和黑色用于简化树的导航和平衡。构建红黑树时必须始终遵守以下规则:
  • 根必须是黑色的。
  • 树的叶子必须是黑色的。
  • 一个红色节点必须有两个黑色子节点。
  • 黑色节点可以有任何颜色的子节点。
  • 从节点到其叶子的路径必须包含相同数量的黑色节点。
  • 新节点被添加到叶子中。
如果将规则 3、4 和 5 一起考虑,您就会理解节点颜色如何让我们更快地导航树:通过黑色节点的路径总是比通过红色节点的路径短。因此,树的总大小由黑色节点的数量决定,称为“黑色高度”。红黑树数据结构在各种编程语言中都有实现。Java的实现网上有很多,这里就不多说了。相反,让我们继续了解 TreeMap 的功能。

来自 SortedMap 和 NavigableMap 接口的方法

和HashMap一样,TreeMap实现了Map接口,也就是说TreeMap拥有HashMap存在的所有方法。但是 TreeMap 还实现了SortedMapNavigableMap接口,因此从中获得了额外的功能。SortedMap是一个扩展Map并添加与排序数据集相关的方法的接口:
  • firstKey():返回地图中第一个元素的键
  • lastKey():返回最后一个元素的键
  • headMap(K end) : 返回一个包含当前地图所有元素的地图,从开始到键为end的元素
  • tailMap(K start):返回一个包含当前地图所有元素的地图,从开始元素到结束
  • subMap(K start, K end):返回一个包含当前地图所有元素的地图,从开始元素到以end为键的元素。
NavigableMap是一个扩展 SortedMap 的接口,并添加了在地图元素之间导航的方法:
  • firstEntry():返回第一个键值对
  • lastEntry():返回最后一个键值对
  • pollFirstEntry():返回并删除第一对
  • pollLastEntry():返回并删除最后一对
  • ceilingKey(K obj):返回大于或等于键obj的最小键k。如果没有这样的键,则返回 null
  • floorKey(K obj):返回小于或等于键obj的最大键k。如果没有这样的键,则返回 null
  • lowerKey(K obj):返回小于键obj的最大键k。如果没有这样的键,则返回 null
  • higherKey(K obj):返回比键obj大的最小键k。如果没有这样的键,则返回 null
  • ceilingEntry(K obj):类似于ceilingKey(K obj)方法,只返回一个键值对(或null)
  • floorEntry(K obj):类似于floorKey(K obj)方法,只返回一个键值对(或null)
  • lowerEntry(K obj):类似于lowerKey(K obj)方法,只返回一个键值对(或null)
  • higherEntry(K obj):类似于higherKey(K obj)方法,只返回一个键值对(或null)
  • descendingKeySet():返回一个 NavigableSet,其中包含以相反顺序排序的所有键
  • descendingMap():返回一个 NavigableMap 包含所有以相反顺序排序的对
  • navigableKeySet():返回一个 NavigableSet 对象,其中包含按存储顺序排列的所有键
  • headMap(K upperBound, boolean incl):返回包含从开始到 upperBound 元素的对的映射。incl参数表示返回的map中是否包含upperBound元素
  • tailMap(K lowerBound, boolean incl) : 功能类似于之前的方法,只返回从 lowerBound 到末尾的对
  • subMap(K lowerBound, boolean lowIncl, K upperBound, boolean highIncl):与前面的方法一样,返回从 lowerBound 到 upperBound 的对;参数 lowIncl 和 highIncl 指示是否在新地图中包含边界元素。
除了通常的构造函数之外,TreeMap 还有另一个接受比较器实例的构造函数。该比较器负责元素存储的顺序。

树图的例子

大量的额外方法似乎是不必要的,但事实证明它们比您乍看之下要有用得多。让我们一起探讨以下示例。想象一下,我们在一家大公司的营销部门工作,我们有一个数据库,其中包含我们要向其展示广告的人员。有两个细节需要记住:
  • 我们需要跟踪每个人的印象数
  • 向未成年人展示广告的算法不同。
让我们创建一个Person类,它将存储每个人的所有相关信息:
public class Person {
   public String firstName;
   public String lastName;
   public int age;

   public Person(String firstName, String lastName, int age) {
       this.firstName = firstName;
       this.lastName = lastName;
       this.age = age;
   }
}
我们在主类 中实现逻辑:
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

public class Main {
   public static void main(String[] args) {
      TreeMap<Person, Integer> map = new TreeMap<>(Comparator.comparingInt(o -> o.age));
      map.put(new Person("John", "Smith", 17), 0);
      map.put(new Person("Ivan", "Petrenko", 65), 0);
      map.put(new Person("Pedro", "Escobar", 32), 0);
      map.put(new Person("Shirley", "Hatfield", 14), 0);
      map.put(new Person("Abby", "Parsons", 19), 0);

      Person firstAdultPerson = map.navigableKeySet().stream().filter(person -> person.age>18).findFirst().get();

       Map<Person, Integer> youngPeopleMap = map.headMap(firstAdultPerson, false);
       Map<Person, Integer> adultPeopleMap = map.tailMap(firstAdultPerson, true);
       showAdvertisementToYoung(youngPeopleMap);
       showAdvertisementToAdult(adultPeopleMap);
   }

   public static void showAdvertisementToYoung(Map map) {}
   public static void showAdvertisementToAdult(Map map) {}
}
在Main类中,创建一个TreeMap,其中每个key代表一个具体的人,每个value是这个月的广告展示次数。我们向构造函数传递一个按年龄对人进行排序的比较器。我们用任意值填充地图。现在我们想在我们的小数据存储库中获取对第一个成年人的引用。我们使用 Stream API 来做到这一点。然后我们得到两个单独的地图,我们将其传递给显示广告的方法。我们可以通过多种方式完成这项任务。TreeMap 类的方法库使我们能够为各种需要创建自定义解决方案。您不需要全部记住它们,因为您始终可以使用 IDE 中的文档或提示。 为了巩固您所学的知识,我们建议您观看我们的 Java 课程中的视频课程
目前为止就这样了!我希望您现在已经清楚 TreeMap 类,并希望您在解决实际编码任务时正确应用它。
评论
  • 受欢迎
你必须先登录才能发表评论
此页面还没有任何评论