1、排序规则
排序就是将一组对象按照某种逻辑顺序重新排列的过程。
主要关注的排序算法是对数组元素而言的排序算法。排序的目的是使数组正序索引的元素也是正序或逆序的。以正序为例,我们给出排序问题中有序有如下命题:
<排序算法>
* 命题1:任何一个元素不小于其存在的左边元素。
* 命题2:有序数组其任何一个元素不大于其右边的元素。
* 命题3:有序数组其任何一个元素不小于其左边元素且不大于右边元素。
以上的有序的定义及命题是后续各类排序算法的原理基点!!!
在java中元素通常对应的都是对象,这就要求对象在设计之初就要考虑其可对比性(Comparable,java提供的接口),根据对象属性特点implements Comparable,并且重写其compareTo()方法,重写的compareTo()方法应有一下三点特性:
- 自反性:对于所有v,v=v。
- 反对称性:对于所有v<w都有v>w,且v=w时w=v。
- 传递性:对于所有v、w和x,如果v<=w且w<=x,则v<=w。
以上三个特性在数的比较看起来十分正常,是因为数这个对象本身就满足这些特性,所以产生了数的排序算法,但是当设计者自己设计一个类,其compareTo()特性就需要自己来设定,很有可能漏掉以上三种特性中的某一条,从而导致比较时出现错误!
2、排序模板
为方便各种排序算法设计方便,这里先设计一个基于比较方法的排序抽象类(AbstractSort),该类应该具有以下方法:
- sort:用来对数组排序。
- show:遍历数组元素,显示数组。
- isSort:判断数组是否已按照顺序排列。
- exch:交换数组中某两个索引的顺序。
- less:比较一个元素是否小于另一个元素。
抽象类代码如下:
package sort;
public class AbstractSort {
public AbstractSort() {
}
//抽象方法输入Comparable接口的数组,用于比较大小所以必然comparable
public static void sort(Comparable<?>[] a) {
}
//用于判断数组元素大小,即是对元素之间不大于的判定!
public static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
//交换数组中i与j下标的元素,不满足有序的情况下执行交换来满足有序!
public static void exch(Comparable<?>[] a, int i, int j) {
Comparable<?> x = null;
x = a[i];
a[i] = a[j];
a[j] = x;
}
//显示数组
public static void show(Comparable<?>[] a) {
for (int i=0; i<a.length; i++) {
System.out.println(a[i]);
}
}
//判断是否正确排序
public static boolean isSort(Comparable<?>[] a){
for (int i=1; i<a.length;i++) {
if(less(a[i],a[i-1])) return false;
}
return true;
}
}
拥有了抽象类,那么大部分基于比较的排序算法都可以继承该类重写sort方法来实现了。
3、排序算法比较原则
研究一个排序算法时,我们需要计算其运行时间与内存使用情况来综合评判一个算法的优劣,或是评判该算法的适用场景。
3.1、运行时间
评估一个算法的运行时间,要计算排序算法在不同随机输入下的基本操作的次数(元素比较次数、元素交换次数,或是读写数组的次数)。
在研究排序算法时,我们需要计算比较和交换的数量。对于不交换元素的算法,我们会计算访问数组的次数。
下面引出一个重要命题:
* 命题3:没有任何一种基于比较的算法能够保证对N长度的数组排序的比较次数少于lgN!。
log2N!约为N log2N,也就是说基于比较的排序算法的时间复杂度的极限为O(N lgN)!
3.2、内存使用
排序算法的额外内存开销和运行时间是同等重要的。排序算法可以分为以下两类:除了函数调用所需的栈和固定数目的实例变量之外无需额外内存的原地排序算法,以及需要额外内存空间来存储数组副本的其他算法。
4、小结
理解排序算法依托于Comparable<>接口,需要重写compareTo方法。理解排序算法的定义:任何一个元素不小于其存在的左边元素,和引申的两个命题。理解排序算法性能的评价指标:时间复杂度&空间复杂度。后续将会介绍各个常用的排序算法(冒泡排序&选择排序&插入排序,归并排序&快速排序,堆排序)