1将各种数据排序
在一个有序的数组中查找一个元素比在一个无序的数组中查找要简单得多。
也就是说:
- 排序是为了查找而服务的。
1.1交易事务
排序算法的一种典型应用就是商业数据的处理。
一家成功的商业公司需要能够处理数百万的交易数据。
1.2指针排序
我们全书基于java
的算法都被称为“指针排序”。
因为我们只处理了元素的引用,而并没有移动数据本身。
- 除了原始数据类型之外,我们的操作总是数据的引用,而不是数据的本身。
1.3不可变的键
如果在排序之后,用例还能够修改元素的键值,那么数据就很可能不再是有序的了。
因此:
- 在
java
中,可以使用不可变的数据类型来作为键来避免这个问题。
例如:String,Integer,Double,File
1.4廉价的交换
对于任意大小的元素,使用引用使得在一般情况下交换的成本和比较的成本几乎相同(代价只是需要额外的空间来存储这些引用)。
1.5多种排序方法
在许多应用中,我们希望能够根据情况将一组对象按照不同的方式排序。
因此:
Comparator接口
允许我们为任意数据类型定义任意多种排序方法。- 用
Comparator接口
来代替Comparable接口
能够更好地将数据类型的定义和两个该类型的对象的比较的定义区分开来。
1.6多键数组
一般在应用程序中,一个元素的多种属性都可以被用作排序的键。
因此:
Comparator接口
正好合适,我们可以定义多种比较器。
/**
* 使用了Comparator的插入排序
* @Author: AZhu
* @Date: 2021/2/24 16:18
*/
public class InsertionByCpt {
/**
* 使用了Comparator的插入排序
* @param a
* @param c
*/
public static void sort(Object[] a, Comparator c) {
int N = a.length;
for (int i = 0; i < N; i++) {
for (int j = i; j > 0 && less(c, a[j], a[j - 1]); j--) {
exch(a, j, j - 1);
}
}
}
/**
* 使用比较器c来比较对象v和w的大小
* @param c
* @param v
* @param w
* @return
*/
private static boolean less(Comparator c, Object v, Object w) {
return c.compare(v, w) < 0;
}
/**
* 在数组a中交换索引i和j中的元素
* @param a
* @param i
* @param j
*/
private static void exch(Object[] a, int i, int j) {
Object t = a[i];
a[i] = a[j];
a[j] = t;
}
}
/**
* 记录交易信息的类
* @Author: AZhu
* @Date: 2021/2/24 16:38
*/
public class Transaction {
private final String who;
private final Date when;
private final double amount;
public Transaction(String who, Date when, double amount) {
this.who = who;
this.when = when;
this.amount = amount;
}
public static class WhoOrder implements Comparator<Transaction> {
@Override
public int compare(Transaction v, Transaction w) {
return v.who.compareTo(w.who);
}
}
public static class WhenOrder implements Comparator<Transaction> {
@Override
public int compare(Transaction v, Transaction w) {
return v.when.compareTo(w.when);
}
}
public static class HowMuchOrder implements Comparator<Transaction> {
@Override
public int compare(Transaction v, Transaction w) {
if (v.amount < w.amount) {
return -1;
}
if (v.amount > w.amount) {
return +1;
}
return 0;
}
}
}
1.7使用比较器实现优先队列
按照以下步骤来扩展算法2.6来实现支持比较器。
- 导入java.util.Comparator
- 为
MaxPQ
添加一个成员变量comparator
以及一个构造器,构造器接受一个比较器作为参数并用于初始化comparator
。 - 在
less()
中检查comparator
属性是否为null
,如果不是就用它来比较。
1.8稳定性
如果一个排序算法能够保留数组中重复元素的相对位置则可以成为是稳定的。
2我们应该使用哪种排序算法
各种排序算法的性能特点:
算法 | 是否稳定 | 是否为原地排序 | 时间复杂度 | 空间复杂度 | 备注 |
---|---|---|---|---|---|
选择排序 | 否 | 是 | N^2 | 1 | |
插入排序 | 是 | 是 | N~N^2 | 1 | 取决于输入元素的排列情况 |
希尔排序 | 否 | 是 | NlogN? N^(6/5)? | 1 | |
归并排序 | 是 | 否 | NlogN | N | |
快速排序 | 否 | 是 | NlogN | lgN | 运行效率由概率提供保证 |
三向快速排序 | 否 | 是 | N~NlogN | lgN | 运行效率由概率提供保证,也取决于输入元素的分布情况 |
堆排序 | 否 | 是 | NlogN | 1 |
快速排序是最快的通用排序算法。
如果稳定很重要而空间又不是问题,归并排序可能是最好的。
2.1将原始数据类型排序
如果我们只是在将一大组数进行排序的话,
跳过引用可以为我们节省存储所有引用所需的空间和通过引用来访问数字的成本,
更不用说那些调用compareTo()
和less()
方法的开销了。
2.2java系统库的排序算法
java系统的主要排序方法位于:java.util.Arrays.sort()
实际上代表了一系列排序方法:
- 每种原始数据类型都有一个不同的排序方法
- 一个适用于所有实现了
Comparable()接口
的数据类型的排序方法 - 一个适用于所有实现了比较器
Comparator()
的数据类型的排序方法
java系统的程序员选择:
- 对原始数据类型使用三向切分的快速排序,
- 对引用类型使用归并排序。
3问题的归约
归约
指的是为了解决某个问题而发明的算法正好可以用于解决另一种问题。
排序算法也可以用于解决其他非排序问题:
3.1找出重复元素
- 首先将数组排序
- 然后遍历有序的数组,记录连续出现的重复元素即可。
/**
* 统计a[]中不重复元素的个数
* @param a
* @return
*/
public static int countNonredundant(Comparable[] a) {
Quick.sort(a);
int count = 1;
for (int i = 1; i < a.length; i++) {
if (a[i].compareTo(a[i - 1]) != 0) {
count++;
}
}
return count;
}
3.2排名
某个排列和标准排列(即每个元素都在正确位置上的排列)的Kandall tau
距离就是其中逆序数对的数量。
3.3优先队列
例:
- 找出输入流中
M
个最大的元素 - 将
M
个输入流归并为一个有序的输出流
3.4中位数和顺序统计
- 找出一组元素的中位数
或
- 找出一组数中的第
k
小的元素
实现方法:
低级实现:
先对数组排序,然后数组中的k
个最小的元素就是数组的前k
个元素。
高级实现:
使用切分
的方法,如下:
/**
* 找到一组数中的第k小元素
* @param a
* @param k
* @return
*/
public static Comparable select(Comparable[] a, int k) {
StdRandom.shuffle(a);
int lo = 0, hi = a.length - 1;
while (hi > lo) {
int j = Quick.partition(a, lo, hi);//切分
if (j == k) {
return a[k];//找到了
} else if (j > k) {
hi = j - 1;//k在左边
} else {
lo = j + 1;//k在右边
}
}
return a[k];
}
- 平均来说,基于切分的选择算法的运行时间是线性级别的。
4排序应用一览
- 商业计算
- 信息搜索
- 运筹学
- 事件驱动模拟
- 数值计算
- 组合搜索
Prim
算法和Dijkstra
算法Kruskal
算法- 霍夫曼算法
- 字符串处理
5答疑
Q:java的系统库中有优先队列这种数据类型吗?
A:有,请见:java.util.PriorityQueue