Chapter 25: Binary Search Trees and Ordered Sets
25.06Intermediate / Advanced6 topics17 min

Ordered maps and sets

Java's ordered collections: TreeMap, TreeSet, the NavigableMap and NavigableSet interfaces, sub-map views and how comparators define equality.

Learning goals

  • Choose between HashMap and TreeMap and state their costs
  • Use floor, ceiling, lower, higher, first, last and poll methods
  • Take head, tail and sub views of a map or set
  • Write a comparator that is consistent, overflow-safe and defines the right notion of equality

Prerequisites

The BST invariantBST queriesHash tables

You rarely write a BST by hand in Java: TreeMap and TreeSet already provide a balanced one with the whole ordered-query vocabulary of this chapter. Knowing the API, and its comparator rules, is what you use in practice.

25.06.1

TreeMap

TreeMap<K, V> is a map whose keys are kept in sorted order. In the JDK it is a red-black tree, a self-balancing BST (the topic of the next chapter), so get, put and remove cost O(log n) even for sorted input. Iteration visits keys in increasing order. A HashMap is O(1) on average but has no order and no neighbour queries.

Use TreeMap when you need sorted iteration, nearest-key lookups, or range views. Keys must be comparable (or a Comparator must be given), and a null key throws NullPointerException with natural ordering.

HashMapTreeMap
get / put / removeO(1) averageO(log n) worst case
Iteration orderunspecifiedsorted by key
floor / ceiling / range viewsnot availableO(log n) per query
null keyallowed (one)NullPointerException (natural order)
Java
1static void treeMapBasics() {
2 TreeMap<Integer, String> m = new TreeMap<>();
3 m.put(50, "fifty"); m.put(30, "thirty"); m.put(70, "seventy"); m.put(20, "twenty");
4 System.out.println(m); // {20=twenty, 30=thirty, 50=fifty, 70=seventy}: sorted by key
5 System.out.println(m.firstKey() + " " + m.lastKey()); // 20 70
6 for (Map.Entry<Integer, String> e : m.entrySet()) System.out.print(e.getKey() + " "); // 20 30 50 70
7}
25.06.2

TreeSet

TreeSet<E> is a sorted set backed by a TreeMap: no duplicates (by the comparison), sorted iteration, O(log n) add, remove and contains. It is the structure to reach for with 'keep the elements ordered while inserting and removing', such as a sweep line's active set or a leaderboard.

first() and last() return the extremes, pollFirst() and pollLast() remove them, and the neighbour methods below find the closest elements to a value. Rule of thumb: a PriorityQueue if you only ever need the extreme; a TreeSet if you also need to delete arbitrary elements or find neighbours.

Java
1static void treeSetBasics() {
2 TreeSet<Integer> s = new TreeSet<>(List.of(40, 10, 30, 20, 30));
3 System.out.println(s); // [10, 20, 30, 40]: sorted, the duplicate 30 is dropped
4 System.out.println(s.first() + " " + s.last()); // 10 40
5 System.out.println(s.pollFirst() + " " + s); // 10 [20, 30, 40]: removes and returns the smallest
6}
25.06.5

Submaps

Range queries come as **views** of the original collection, not copies, in O(log n) to create. headMap(to) holds keys below to (exclusive by default), tailMap(from) keys from from upward (inclusive by default), and subMap(from, to) the **half-open** range [from, to). Overloads take explicit inclusive flags. TreeSet has headSet, tailSet and subSet with the same conventions.

Views are **live**: writes to the view change the original, and the reverse. A view only accepts keys inside its range: put of a key outside throws IllegalArgumentException. Counting the elements in a view with size() costs O(k), not O(log n), because it iterates.

Java
1static void views() {
2 TreeMap<Integer, String> m = new TreeMap<>();
3 for (int k = 10; k <= 50; k += 10) m.put(k, "v" + k); // 10 20 30 40 50
4 System.out.println(m.subMap(20, 40).keySet()); // [20, 30]: [20, 40) is half-open
5 System.out.println(m.subMap(20, true, 40, true).keySet()); // [20, 30, 40]
6 System.out.println(m.headMap(30).keySet() + " " + m.tailMap(30).keySet()); // [10, 20] [30, 40, 50]
7 m.subMap(20, 40).clear(); // removes 20 and 30 from the original map
8 System.out.println(m.keySet()); // [10, 40, 50]
9 try { m.subMap(20, 40).put(99, "x"); } catch (IllegalArgumentException e) { System.out.println("key out of range"); }
10}
25.06.6

Comparator semantics

A TreeMap or TreeSet decides everything by the comparator: two keys are **equal if compare returns 0**, whatever equals says. A TreeSet<String> with String.CASE_INSENSITIVE_ORDER treats "a" and "A" as the same element and keeps only the first; a comparator that orders strings by length would reject every second string of the same length.

A comparator must be consistent: antisymmetric, transitive and stable over time, otherwise the tree misbehaves silently. Avoid the subtraction trick (a, b) -> a - b for ints: it overflows, so compare(Integer.MAX_VALUE, -1) returns a negative number. Use Integer.compare or Comparator.comparingInt. For several fields, chain them with thenComparing, and make sure the comparator distinguishes elements that you want to keep apart.

Java
1static void comparatorPitfalls() {
2 TreeSet<String> ci = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
3 ci.add("a"); ci.add("A"); ci.add("b");
4 System.out.println(ci); // [a, b]: "A" compared equal to "a" and was dropped
5
6 TreeSet<String> byLength = new TreeSet<>(Comparator.comparingInt(String::length));
7 byLength.add("aa"); byLength.add("bb"); byLength.add("c");
8 System.out.println(byLength); // [c, aa]: "bb" has the same length as "aa", so it is "equal"
9
10 Comparator<Integer> broken = (a, b) -> a - b; // overflows near the limits
11 System.out.println(broken.compare(Integer.MAX_VALUE, -1) < 0); // true: MAX_VALUE sorts BEFORE -1 (wrong)
12 System.out.println(Integer.compare(Integer.MAX_VALUE, -1) > 0); // true: the correct answer
13}

Practice ladder

Now try it yourself

Easy

  • Design HashSet with a TreeSet (concept)
  • Find Closest Number to Zero

Medium

  • My Calendar I
  • Contains Duplicate III
  • Online Stock Span with TreeMap

Advanced

  • Sliding Window Median
  • Data Stream as Disjoint Intervals

Key takeaways

  • TreeMap and TreeSet are balanced BSTs: O(log n) operations, sorted iteration, neighbour and range queries.
  • floor, ceiling, lower and higher return null when nothing qualifies.
  • headMap, tailMap and subMap are live views; subMap(from, to) is half-open.
  • The comparator defines equality: compare == 0 means the same key.
  • Never write a comparator as a - b for ints; use Integer.compare.