Elementary Sorts 基本排序
0.前言
本文将会涉及以下几种基本排序方法:选择排序(selection sort),插入排序(insertion sort),希尔排序(shell sort),洗牌(shuffling),凸包(convex hull)等
1.排序的规则
在java中,如果想实现能够对任意数据类型排序的话,就必须要实现comparable接口。
-
1.File类实现comparable接口,并重写compareTo()方法,在此方法中声明比较规则。
-
2.Insertion排序类中,排序方法sort(Comparable[] a)需要接受Comparable类型的数组参数,这样的话,数组元素才能够调用compareTo()方法,声明比较规则。
-
3.客户程序中直接调用Insertion排序类中的sort()方法,并传入任意数据类型的数组。完毕。
例如,在自定义的Date类里面,实现Comparable的方式如下:
public class Date implements Comparable<Date>{
private final int month, day, year;
public Date(int m, int d, int y){
month = m;
day = d;
year = y;
}
//Data类型必须是全序的
public int compareTo(Date that){
if (this.year < that.year ) return -1;
if (this.year > that.year ) return +1;
if (this.month < that.month) return -1;
if (this.month > that.month) return +1;
if (this.day < that.day ) return -1;
if (this.day > that.day ) return +1;
return 0;
}
}
利用compareTo()方法可以轻松地实现比较方法less()和交换方法exch(),这两个方法再排序中的调用率是相当高的。
//比较方法
private static boolean less(Comparable v, Comparable w){
return v.compareTo(w) < 0;
}
//交换方法
private static void exch(Comparable[] a, int i, int j){
Comparable swap = a[i];
a[i] = a[j];
a[j] = swap;
}
关键:compareTo()方法里的比较的数据必须是全序关系(total order)的。例如,实数(标准序)、日期和时间(时序)、字符串(字母序)等。
这里会简单分析全序和偏序的概念:
全序是一种二元关系,集合内任何一对元素在在这个关系下都是相互可比较的。
设集合X上有一全序关系,如果我们把这种关系用 ≤ 表述,则下列陈述对于 X 中的所有 a, b 和 c 成立:
如果 a ≤ b 且 b ≤ a 则 a = b (反对称性)
如果 a ≤ b 且 b ≤ c 则 a ≤ c (传递性)
a ≤ b 或 b ≤ a (完全性)
偏序是一种二元关系,集合内只有部分元素之间在这个关系下是可以比较的。
设R是集合A上的一个二元关系,若R满足:
Ⅰ 自反性:对任意x∈A,有xRx;
Ⅱ 反对称性(即反对称关系):对任意x,y∈A,若xRy,且yRx,则x=y;
Ⅲ 传递性:对任意x, y,z∈A,若xRy,且yRz,则xRz。
则称R为A上的偏序关系。
完全性本身也包括了自反性。所以,全序关系必是偏序关系。
2.选择排序
原理
使用双指针i(图示红色)和j(图示蓝色),指针i遍历序列元素,在i的左边元素都是有序的,在i的右边元素都是无序的。指针j遍历无序元素,当出现小于指针i的指向元素时,互换位置。直至序列全部有序。
实际如图所示:
代码实现
public class Selection{
public static void sort(Comparable[] a){
int N = a.length;
for (int i = 0; i < N; i++){
int min = i;
for (int j = i+1; j < N; j++)
if (less(a[j], a[min]))
min = j;
exch(a, i, min);
}
}
private static boolean less(Comparable v, Comparable w){ /* as before */ }
private static void exch(Comparable[] a, int i, int j){ /* as before */ }
}
时间性能
比较(conpare):需要(N – 1) + (N – 2) + … + 1 + 0 ~ N^2 / 2,即使是有序序列
交换(exchange):需要0 —— N,有序时为0,反序时为N
3.插入排序
原理
使用双指针i(图示红色)和j(图示蓝色),指针i遍历序列元素,在i的左边元素都是有序的,在i的右边元素都是无序的。指针j逆序(从i到0)地遍历有序元素,当出现大于指针i的指向元素时,互换位置。直至序列全部有序。
实际如图所示:
代码实现
public class Insertion{
public static void sort(Comparable[] a){
int N = a.length;
for (int i = 0; i < N; i++)
for (int j = i; j > 0; j--)
if (less(a[j], a[j-1]))
exch(a, j, j-1);
else break;
}
}
private static boolean less(Comparable v, Comparable w){ /* as before */ }
private static void exch(Comparable[] a, int i, int j){ /* as before */ }
}
时间性能
比较(conpare):平均需要 ~N^2 /4,最坏情况是~N^2 /2,最好情况是N
交换(exchange):平均需要 ~N^2 /4,最坏情况是~N^2 /2,最好情况是0
4.希尔排序
原理
希尔排序的基础的插入排序和h排序(h-sort),前面已经分析过插入排序了,这里需要详细地分析h排序。
h排序的规则与插入排序是一致的,唯一的区别在于:指针i(红色箭头)的步长是1,即一位一位地移,而在h排序中,指针i的步长就是h。
因此,希尔排序就是多次是在用h排序(h从大到小,最后一次为1)直至序列有序。
从插入排序的时间性能上看,比较和交换的最坏情况都需要~N^2 /2,深究其原因会发现:有些元素本身较小,需要移动到序列前面,但是移动过程却是一次一次地比较和交换,最终拉低时间性能。而希尔排序的出现就是为了解决此问题。希尔排序中,前几次h排序会以较大的步数移动元素,经过几次h排序后,序列已经基本有序,此时再进行小步移动,直到序列完全有序(此时h为1)。
关键
如何确定h序列是希尔排序的难点。经过大量的实验,目前最常采用的就是h=3h+1,即h序列为:1,4,13,40,121,···
代码实现
public class Shell{
public static void sort(Comparable[] a){
int N = a.length;
int h = 1;
while (h < N/3) h = 3*h + 1; // 1, 4, 13, 40, 121, 364, ...
while (h >= 1){ // h-sort the array.
for (int i = h; i < N; i++){
for (int j = i; j >= h && less(a[j], a[j-h]); j -= h)
exch(a, j, j-h);
}
h = h/3;
}
}
private static boolean less(Comparable v, Comparable w){ /* as before */ }
private static void exch(Comparable[] a, int i, int j){ /* as before */ }
}
时间性能
目前,还没有精确的数学模型可以分析出希尔排序的时间复杂度。经过大量的实验数据分析,对3h+1的步长的希尔排序的比较操作的最坏时间性能为O(N^3/2)。
5.洗牌shuffling
洗牌就是对一个有序或者无序的序列进行随机排序,打乱其原有的次序。
- 如果实现Comparable接口的话,可以重写compateTo()方法:
public int compareTo(Browser that){
double r = Math.random();
if (r < 0.5) return -1;
if (r > 0.5) return +1;
return 0;
}
- 如果单独封装shuffling()方法的话,可以使用以下代码。原理:使用指针i遍历序列元素,每当指向一个新元素就产生一个0——i的随机数k。将i元素与k元素,直至指针i遍历全部元素。
public static void shuffle(Object[] a){
int N = a.length;
for (int i = 0; i < N; i++){
int r = StdRandom.uniform(i + 1);
exch(a, i, r);
}
}
6.凸包convex hull
coming soon