Java 中排序的稳定性 笔记

3 阅读3分钟

Java 中排序的稳定性详解

1. 什么是排序稳定性?

排序稳定性是指:如果两个相等的元素在排序前的相对顺序,在排序后保持不变,那么这个排序算法就是稳定的。

示例:

排序前:[(A, 2), (B, 1), (C, 2), (D, 3)]
按数字排序后:
稳定排序:[(B, 1), (A, 2), (C, 2), (D, 3)]  // A 仍在 C 前面
不稳定排序:[(B, 1), (C, 2), (A, 2), (D, 3)]  // A 和 C 的相对顺序改变

2. Java 中常用排序算法的稳定性

2.1 稳定排序算法

算法使用场景稳定性
归并排序Arrays.sort(Object[])
Collections.sort()
稳定
TimSortJava 7+ 的对象排序稳定
插入排序小数组或部分有序稳定
冒泡排序教学用途稳定

2.2 不稳定排序算法

算法使用场景稳定性
快速排序Arrays.sort(int[])不稳定
堆排序PriorityQueue不稳定
选择排序教学用途不稳定

3. Java 具体排序方法的稳定性

3.1 Arrays.sort()

// 基本类型数组 - 不稳定(使用双轴快速排序)
int[] intArray = {5, 2, 5, 1, 3};
Arrays.sort(intArray);  // 不稳定

// 对象数组 - 稳定(使用归并排序/TimSort)
Integer[] objArray = {5, 2, 5, 1, 3};
Arrays.sort(objArray);  // 稳定

// 自定义对象数组
Person[] people = {new Person("Alice", 25), new Person("Bob", 25)};
Arrays.sort(people, Comparator.comparing(Person::getAge));  // 稳定

3.2 Collections.sort()

List<Integer> list = Arrays.asList(5, 2, 5, 1, 3);
Collections.sort(list);  // 稳定

List<Person> peopleList = new ArrayList<>();
Collections.sort(peopleList, Comparator.comparing(Person::getAge));  // 稳定

3.3 Stream API 排序

List<Person> sorted = people.stream()
    .sorted(Comparator.comparing(Person::getAge)
            .thenComparing(Person::getName))
    .collect(Collectors.toList());  // 稳定

4. 实际应用示例

4.1 多级排序

class Student {
    String name;
    int grade;
    int age;
    
    // 按成绩排序,成绩相同的按年龄排序
    public static void stableMultiSort(List<Student> students) {
        // 先按年龄排序
        Collections.sort(students, Comparator.comparingInt(s -> s.age));
        // 再按成绩排序 - 稳定排序会保持年龄顺序
        Collections.sort(students, Comparator.comparingInt(s -> s.grade));
        
        // 或者使用 thenComparing
        Collections.sort(students, 
            Comparator.comparingInt((Student s) -> s.grade)
                     .thenComparingInt(s -> s.age));
    }
}

4.2 何时需要稳定排序?

  1. 多关键字排序:先按次要关键字排序,再按主要关键字排序
  2. 用户界面:保持用户熟悉的顺序
  3. 版本控制:保持历史记录的相对顺序
  4. 事务处理:保持时间戳顺序

5. 自定义稳定排序示例

public class StableSortExample {
    
    static class Employee {
        String name;
        String department;
        LocalDate hireDate;
        
        // 按部门排序,同部门的按入职时间排序
        public static void sortEmployees(List<Employee> employees) {
            // 使用稳定排序
            Collections.sort(employees, 
                Comparator.comparing((Employee e) -> e.department)
                         .thenComparing(e -> e.hireDate));
        }
    }
    
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "IT", LocalDate.of(2020, 1, 1)),
            new Employee("Bob", "HR", LocalDate.of(2019, 6, 1)),
            new Employee("Charlie", "IT", LocalDate.of(2021, 3, 1))
        );
        
        // 稳定排序会保证:
        // 1. 先按部门排序
        // 2. 同部门的员工保持按入职时间的顺序
        Employee.sortEmployees(employees);
    }
}

6. 性能考虑

排序类型平均时间复杂度空间复杂度稳定性
TimSort (Java对象)O(n log n)O(n)稳定
双轴快速排序 (基本类型)O(n log n)O(log n)不稳定
插入排序O(n²)O(1)稳定

7. 最佳实践建议

  1. 默认使用稳定排序Collections.sort() 和对象数组的 Arrays.sort()
  2. 基本类型性能优先:对基本类型数组使用 Arrays.sort(),不关心稳定性
  3. 多级排序:使用 Comparator.thenComparing()
  4. 自定义比较器:确保比较逻辑与稳定性需求一致
  5. 文档说明:在API文档中说明排序的稳定性特性

总结

  • Java 中对象排序默认是稳定的(使用 TimSort)
  • 基本类型排序不稳定(出于性能考虑)
  • 稳定排序在多关键字排序场景中非常重要
  • 了解排序稳定性有助于写出更可预测的代码