这里主要对java内置排序的复习
int[] arr = {1, 5, 3, 4, 2, 9, 8, 7, 6};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); // 输出 1 2 3 4 5 6 7 8 9
来看下sort源码
public static void sort(Object[] a) {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a); // 传统归并排序
else
ComparableTimSort.sort(a, 0, a.length, null, 0, 0); // 优化的归并排序
}
分为传统归并排序和优化的归并排序
打开传统归并的源码
上面写着,将在未来的版本移除,之后可能就没有传统的方法了
/**
* Tuning parameter: list size at or below which insertion sort will be
* used in preference to mergesort.
* To be removed in a future release.
*/
private static final int INSERTIONSORT_THRESHOLD = 7;
/**
* Src is the source array that starts at index 0 -> 原数组
* Dest is the (possibly larger) array destination with a possible offset -> 目标数组
* low is the index in dest to start sorting -> 需要排序的起始下标
* high is the end index in dest to end sorting -> 需要排序的终止下标
* off is the offset to generate corresponding low, high in src -> 偏移量
* To be removed in a future release.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private static void mergeSort(Object[] src,
Object[] dest,
int low,
int high,
int off) {
int length = high - low;
// 如果需要排序的长度小于INSERTIONSORT_THRESHOLD,上面定义了为7
// 将每个元素于前面的元素比较,如果小于前面的元素,交换数据
// 相当于插入排序,把小的值放在前面
// Insertion sort on smallest arrays
if (length < INSERTIONSORT_THRESHOLD) {
for (int i=low; i<high; i++)
for (int j=i; j>low &&
((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
swap(dest, j, j-1);
return;
}
//进行归并排序
// Recursively sort halves of dest into src
int destLow = low;
int destHigh = high;
low += off;
high += off;
int mid = (low + high) >>> 1;
mergeSort(dest, src, low, mid, -off);
mergeSort(dest, src, mid, high, -off);
// If list is already sorted, just copy from src to dest. This is an
// optimization that results in faster sorts for nearly ordered lists.
if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
System.arraycopy(src, low, dest, destLow, length);
return;
}
// Merge sorted halves (now in src) into dest
for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
dest[i] = src[p++];
else
dest[i] = src[q++];
}
}
再来看下Comparable
接口的compareTo
的注释(部分)
* @param o the object to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
*
* @throws NullPointerException if the specified object is null
* @throws ClassCastException if the specified object's type prevents it
* from being compared to this object.
*/
public int compareTo(T o);
当a.compareTo(b)
时
他具体怎么实现的,我没找到。。
所以在代码中
dest[j-1].compareTo(dest[j]) > 0
,表示当dest[j-1]
大于dest[j]
时
需要进行插入排序,将两个数交换
所以,java内置传统归并的思路
Integer[] arr = {1, 5, 3, 4, 2, 9, 8, 7, 6};
Arrays.sort(arr, Collections.reverseOrder());
System.out.println(Arrays.toString(arr)); // 输出 9 8 7 6 5 4 3 2 1
可以发现,降序比升序多了一个Collections.reverseOrder()
,并且,arr的数据类型由int变成了Integer包装类
// Arrays.sort源码
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
sort具有重载方法,多了一个Comparator
参数
并且其归并排序的插入排序代码改为了
// Insertion sort on smallest arrays
if (length < INSERTIONSORT_THRESHOLD) {
for (int i=low; i<high; i++)
for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
swap(dest, j, j-1);
return;
}
再回头看下Collections.reverseOrder
// Collections.reverseOrder源码
public static <T> Comparator<T> reverseOrder() {
return (Comparator<T>) ReverseComparator.REVERSE_ORDER;
}
private static class ReverseComparator
implements Comparator<Comparable<Object>>, Serializable {
// ...
static final ReverseComparator REVERSE_ORDER
= new ReverseComparator();
public int compare(Comparable<Object> c1, Comparable<Object> c2) {
return c2.compareTo(c1);
}
// ...
}
通过源码发现,它实现了Comparator
,并且实现了Comparator
的compare
方法,
在归并代码中,其compare
操作为dest[j].compareTo(dest[j-1])
当dest[j]
大于dest[j-1]
时进行数据交换,插入排序
然后就把大的数放在了前面,实现了逆序
好了,Collections.reverseOrder
应该差不多了
然后
sort的第二个参数为Comparator,Comparator super T> c
,其中 super T> c
表示为该类型可以为T或者其父类型
那么我们也可写一个Comparator
来实现compare
Integer[] arr = {1, 5, 3, 4, 2, 9, 8, 7, 6};
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
// 或者使用lambda表达式
// Arrays.sort(arr, (o1, o2) -> o2 - o1);
System.out.println(Arrays.toString(arr)); // 输出为 9 8 7 6 5 4 3 2 1
Comparator
就相当于一个比较器,控制着归并排序中的插入排序的比较
可以控制具体比较的对象和比较的方向
还有一个问题
当arr类型为Interger包装类时,升序降序都正常运行
当用于测试的arr数组类型为int时,其升序排序是正确的,但是降序排序报错
java: 对于sort(int[],java.util.Comparator<java.lang.Object>), 找不到合适的方法
方法 java.util.Arrays.<T>sort(T[],java.util.Comparator<? super T>)不适用
(推论变量 T 具有不兼容的上限
等式约束条件:int
下限:java.lang.Object)
方法 java.util.Arrays.<T>sort(T[],int,int,java.util.Comparator<? super T>)不适用
(无法推断类型变量 T
(实际参数列表和形式参数列表长度不同))
可以看到sort的实现
public static void sort(Object[] a)
public static void sort(T[] a, Comparator super T> c)
第二个方法T[] a
只能存放对象,不能存放基本数据类型,如果放int类型或报错,放入Integer则正常
int是基本数据类型之一,不需要实例化就能用
Integer是int的包装类,需要实例化
两者存在自动装箱封箱,可以适应不同需求
但这里使用的是数组,存在一种“容器”,该“容器”里面存放了两种类型的数据,所以需要手动转化
好了,上面搞清楚了简单的升序降序排序,这里使用Comparator
来实现一些复杂的排序
假设有以下类
class Person {
String name;
int age;
int sex;
// ... getter setter toString constructor
}
List<Person> people = Arrays.asList(
new Person("abc", 12, 0),
new Person("aac", 23, 0),
new Person("bfc", 14, 1),
new Person("fas", 21, 1),
new Person("haf", 21, 0),
new Person("haf", 12, 1),
new Person("fas", 21, 0),
new Person("abc", 13, 0)
);
// 根据名称排序(单个属性)
// 改变o1和o2位置可以改为升序降序
people.sort(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
// return o1.getAge() - o2.getAge();
return o1.getName().compareTo(o2.getName());
}
});
for (Person person : people) {
System.out.println(person);
}
// Person{name='aac', age=23, sex=0}
// Person{name='abc', age=12, sex=0}
// Person{name='abc', age=13, sex=0}
// Person{name='bfc', age=14, sex=1}
// Person{name='fas', age=21, sex=1}
// Person{name='fas', age=21, sex=0}
// Person{name='haf', age=21, sex=0}
// Person{name='haf', age=12, sex=1}
// 当单个属性相同时,剩余的属性没有按照严格的顺序排列
// 多维排序
// 先按年龄大小排,逆序
// 当年龄相同时,再按名字的字典序排升序
// 当名字的字典序相同时,再按性别排升序,这里性别用了0和1代替
people.sort(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
if (o1.getAge() == o2.getAge()) {
if (o1.getName().equals(o2.getName())) {
return o1.getSex() - o2.getSex();
} else {
return o1.getName().compareTo(o2.getName());
}
} else {
return o2.getAge() - o1.getAge();
}
}
});
for (Person person : people) {
System.out.println(person);
}
// Person{name='aac', age=23, sex=0}
// Person{name='fas', age=21, sex=0}
// Person{name='fas', age=21, sex=1}
// Person{name='haf', age=21, sex=0}
// Person{name='bfc', age=14, sex=1}
// Person{name='abc', age=13, sex=0}
// Person{name='abc', age=12, sex=0}
// Person{name='haf', age=12, sex=1}
// 可以看到,已经按要求排好序了
其中,people.sort
的源码中,主要也是Arrays.sort
实现的
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
由于之前也用过python3排序,这里写记录下
from functools import cmp_to_key
a = [[1, 1, 1],
[1, 2, 1],
[1, 1, 4],
[2, 3, 1],
[3, 2, 1],
[4, 3, 2],
[1, 0, 2],
[1, 0, 1]]
# 先按第0个数据排升序,第0个数据相同时按第1个数据排,第1个数据相同时按第2个数据排
b = sorted(a, key=lambda x: (x[0], x[1], x[2]))
for i in b:
print(i)
# [1, 0, 1]
# [1, 0, 2]
# [1, 1, 1]
# [1, 1, 4]
# [1, 2, 1]
# [2, 3, 1]
# [3, 2, 1]
# [4, 3, 2]
# 先按第0个数据排逆序,第0个数据相同时按第1个数据排升序,第1个数据相同时按第2个数据排升序
def func(x, y):
if x[0] == y[0]:
if x[1] == y[1]:
return x[2] - y[2]
else:
return x[1] - y[1]
else:
return y[0] - x[0]
b = sorted(a, key=cmp_to_key(func))
for i in b:
print(i)
# [4, 3, 2]
# [3, 2, 1]
# [2, 3, 1]
# [1, 0, 1]
# [1, 0, 2]
# [1, 1, 1]
# [1, 1, 4]
# [1, 2, 1]