1. TreeSet 概述

TreeSet 是 Java 集合框架中的一个重要实现类,它基于红黑树(Red-Black tree)实现,具有以下特点:

  • 有序性:元素按照自然顺序或自定义比较器排序

  • 唯一性:不允许重复元素

  • 非同步:非线程安全,需要外部同步

  • 基于 NavigableSet 接口:支持导航方法

2. 底层数据结构:红黑树

红黑树特性

红黑树是一种自平衡的二叉查找树,具有以下性质:

  1. 每个节点要么是红色,要么是黑色

  2. 根节点是黑色

  3. 所有叶子节点(NIL)都是黑色

  4. 红色节点的子节点必须是黑色

  5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点

TreeSet 中的红黑树实现

// TreeSet 实际上使用 TreeMap 来实现
public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable {
    
    // 使用 TreeMap 作为存储
    private transient NavigableMap<E,Object> m;
    
    // 虚拟值,用于 TreeMap
    private static final Object PRESENT = new Object();
}

3. TreeSet 的基本使用

创建 TreeSet

import java.util.TreeSet;
import java.util.Comparator;

// 1. 使用自然排序
TreeSet<Integer> naturalSet = new TreeSet<>();

// 2. 使用自定义比较器
TreeSet<String> customSet = new TreeSet<>(new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length(); // 按字符串长度排序
    }
});

// 3. 使用 Lambda 表达式
TreeSet<String> lambdaSet = new TreeSet<>(
    (s1, s2) -> s1.length() - s2.length()
);

基本操作示例

public class TreeSetBasicExample {
    public static void main(String[] args) {
        TreeSet<Integer> numbers = new TreeSet<>();
        
        // 添加元素
        numbers.add(5);
        numbers.add(2);
        numbers.add(8);
        numbers.add(1);
        numbers.add(5); // 重复元素,不会被添加
        
        System.out.println("TreeSet: " + numbers); // [1, 2, 5, 8]
        
        // 删除元素
        numbers.remove(2);
        System.out.println("After removal: " + numbers); // [1, 5, 8]
        
        // 检查元素是否存在
        System.out.println("Contains 5: " + numbers.contains(5)); // true
        
        // 获取大小
        System.out.println("Size: " + numbers.size()); // 3
        
        // 清空集合
        numbers.clear();
        System.out.println("After clear: " + numbers); // []
    }
}

4. 导航方法详解

导航方法示例

public class TreeSetNavigationExample {
    public static void main(String[] args) {
        TreeSet<Integer> set = new TreeSet<>();
        set.add(10);
        set.add(20);
        set.add(30);
        set.add(40);
        set.add(50);
        
        // 第一个和最后一个元素
        System.out.println("First: " + set.first()); // 10
        System.out.println("Last: " + set.last());   // 50
        
        // 小于等于指定值的最大元素
        System.out.println("Floor(25): " + set.floor(25)); // 20
        
        // 大于等于指定值的最小元素
        System.out.println("Ceiling(25): " + set.ceiling(25)); // 30
        
        // 严格小于指定值的最大元素
        System.out.println("Lower(30): " + set.lower(30)); // 20
        
        // 严格大于指定值的最小元素
        System.out.println("Higher(30): " + set.higher(30)); // 40
        
        // 获取并移除第一个元素
        System.out.println("Poll first: " + set.pollFirst()); // 10
        System.out.println("After pollFirst: " + set); // [20, 30, 40, 50]
        
        // 获取并移除最后一个元素
        System.out.println("Poll last: " + set.pollLast()); // 50
        System.out.println("After pollLast: " + set); // [20, 30, 40]
    }
}

5. 子集操作

子集方法示例

public class TreeSetSubsetExample {
    public static void main(String[] args) {
        TreeSet<Integer> set = new TreeSet<>();
        for (int i = 1; i <= 10; i++) {
            set.add(i);
        }
        
        // 获取子集 [fromElement, toElement)
        SortedSet<Integer> subSet = set.subSet(3, 7);
        System.out.println("Subset [3,7): " + subSet); // [3, 4, 5, 6]
        
        // 获取头部子集(小于 toElement)
        SortedSet<Integer> headSet = set.headSet(5);
        System.out.println("HeadSet (<5): " + headSet); // [1, 2, 3, 4]
        
        // 获取尾部子集(大于等于 fromElement)
        SortedSet<Integer> tailSet = set.tailSet(5);
        System.out.println("TailSet (>=5): " + tailSet); // [5, 6, 7, 8, 9, 10]
        
        // 使用 NavigableSet 的精确控制方法
        NavigableSet<Integer> descendingSet = set.descendingSet();
        System.out.println("Descending set: " + descendingSet); // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
    }
}

6. 自定义对象排序

实现 Comparable 接口

class Student implements Comparable<Student> {
    private String name;
    private int score;
    
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    
    @Override
    public int compareTo(Student other) {
        // 先按分数排序,分数相同按姓名排序
        int scoreCompare = Integer.compare(this.score, other.score);
        return scoreCompare != 0 ? scoreCompare : this.name.compareTo(other.name);
    }
    
    @Override
    public String toString() {
        return name + "(" + score + ")";
    }
}

public class ComparableExample {
    public static void main(String[] args) {
        TreeSet<Student> students = new TreeSet<>();
        students.add(new Student("Alice", 85));
        students.add(new Student("Bob", 92));
        students.add(new Student("Charlie", 85)); // 同分数,按姓名排序
        
        System.out.println("Students: " + students);
        // 输出: [Alice(85), Charlie(85), Bob(92)]
    }
}

使用 Comparator

class Employee {
    private String name;
    private String department;
    private double salary;
    
    public Employee(String name, String department, double salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }
    
    // getters...
    public String getName() { return name; }
    public String getDepartment() { return department; }
    public double getSalary() { return salary; }
    
    @Override
    public String toString() {
        return name + "[" + department + "]-$" + salary;
    }
}

public class ComparatorExample {
    public static void main(String[] args) {
        // 按部门排序,同部门按薪资降序
        TreeSet<Employee> employees = new TreeSet<>(
            Comparator.comparing(Employee::getDepartment)
                     .thenComparing(Comparator.comparingDouble(Employee::getSalary).reversed())
        );
        
        employees.add(new Employee("John", "IT", 75000));
        employees.add(new Employee("Jane", "HR", 65000));
        employees.add(new Employee("Mike", "IT", 80000));
        
        System.out.println("Employees:");
        employees.forEach(System.out::println);
        // 输出: 
        // Mike[IT]-$80000.0
        // John[IT]-$75000.0
        // Jane[HR]-$65000.0
    }
}

7. 性能分析

时间复杂度

操作 时间复杂度 说明
add() O(log n) 插入操作
remove() O(log n) 删除操作
contains() O(log n) 查找操作
first()/last() O(1) 获取首尾元素
floor()/ceiling() O(log n) 导航操作

空间复杂度

  • O(n):需要存储 n 个元素

8. 使用注意事项

线程安全

// TreeSet 非线程安全,需要外部同步
Set<Integer> synchronizedSet = Collections.synchronizedSet(new TreeSet<>());

// 或者在多线程环境下使用 ConcurrentSkipListSet
// NavigableSet<Integer> concurrentSet = new ConcurrentSkipListSet<>();

比较一致性

public class ConsistencyExample {
    public static void main(String[] args) {
        TreeSet<String> set = new TreeSet<>();
        set.add("apple");
        set.add("banana");
        set.add("cherry");
        
        // 错误:修改会影响排序,但 TreeSet 不会重新排序
        // 应该避免在添加到 TreeSet 后修改影响排序的字段
        
        System.out.println("Original set: " + set);
        
        // 正确的做法是删除后重新添加,如果需要修改
        String toModify = "apple";
        if (set.remove(toModify)) {
            // 修改逻辑
            String modified = "apricot";
            set.add(modified);
        }
        
        System.out.println("Modified set: " + set);
    }
}

9. 实际应用场景

场景1:维护有序数据

public class ScoreManager {
    private TreeSet<Integer> scores;
    
    public ScoreManager() {
        scores = new TreeSet<>(Comparator.reverseOrder()); // 降序排列
    }
    
    public void addScore(int score) {
        scores.add(score);
    }
    
    public List<Integer> getTopScores(int n) {
        return scores.stream()
                    .limit(n)
                    .collect(Collectors.toList());
    }
    
    public Integer getScoreHigherThan(int threshold) {
        return scores.higher(threshold);
    }
}

场景2:范围查询

public class RangeQueryExample {
    public static void main(String[] args) {
        TreeSet<LocalDate> dates = new TreeSet<>();
        dates.add(LocalDate.of(2023, 1, 15));
        dates.add(LocalDate.of(2023, 3, 20));
        dates.add(LocalDate.of(2023, 5, 10));
        dates.add(LocalDate.of(2023, 7, 5));
        dates.add(LocalDate.of(2023, 9, 30));
        
        LocalDate start = LocalDate.of(2023, 2, 1);
        LocalDate end = LocalDate.of(2023, 8, 1);
        
        // 获取指定范围内的日期
        SortedSet<LocalDate> range = dates.subSet(start, end);
        System.out.println("Dates between " + start + " and " + end + ": " + range);
    }
}

总结

TreeSet 是一个功能强大的有序集合实现,基于红黑树数据结构提供了高效的排序和导航操作。它在需要维护元素顺序、进行范围查询或需要快速查找最值的场景中非常有用。使用时需要注意其非线程安全的特性,以及在自定义对象排序时确保比较逻辑的一致性。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐