第二章 排序(一)
2.1 初级排序算法
2.1.1 游戏规则
- 排序的目标:将所有元素的主键按照某种方式排列。
- 排序类算法的模板:
public class Example{
public static void sort(Comparable[] a){}//对实现comparable接口对象组成的数组排序
public static boolean less(Comparale v, Comparable w){//v是否小于w
return v.compareTo(w) < 0;
}
public static void exch(Comparable a, int i, int j){//交换索引为i和j的对象
Comparable t = a[i]; a[i] = a[j]; a[j] = t;
}
public static void show(Comparable[] a){//在单行中打印数组
for(int i = 0; i < a.length; i++)
StdOut.print(a[i] + " ");
StdOut.println();
}
public static boolean isSorted(Comparable[] a){//测试数组是否有序
for(int i = 1; i < a.length; i++)
if(less(a[i], a[i-1]))//排序后索引较大的主键大于等于索引较小的主键
return false;
}
}
- 在创建自己的数据类型是,只需要实现Comparable接口,就可以保证可排序,需要实现compareTo()方法来定义目标类型对象的自然次序。
2.1.2 选择排序
- 不断选择剩余元素之中的最小者,与剩余元素中最前面的元素交换。
- 算法如下:
public class Selection{
public static void sort(Comparable[] a){//将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);
}
}
}
- 交换次数总是N,算法效率取决于比较次数
- 比较次数大约为N2/2
- 选择排序的特点:
2.1.3 插入排序
- 将元素一个一个插入到其他有序元素中,其余所有元素在插入前都要右移一位。
- 算法如下:
public class Insertion{
public static void sort(Comparable[] a){
int N = a.length;
for(int i = 1; i < N; i++)//将a[i]插入到a[i-1]、a[i-2]、a[i-3]...之中
for(int j = i; j > 0 && less(a[j], a[j-1]); j--)
exch(a, j, j-1);//a[i]与a[0]到a[i-1]中所有比它小的元素依次有序交换
}
}
- 插入排序所需时间取决于输入中元素的初始顺序
- 平均情况下需要N<sup>2</sup>/4次比较以及N2/4次交换;最坏情况下需要N<sup>2</sup>/2次比较以及N2/2次交换;最好情况下需要N-1次比较和0次交换。
- 插入排序对于部分有序的数组十分高效
2.1.5 比较两种排序算法
- 对于随机排序的无重复主键的数组,插入排序和选择排序的运行时间是平方级别的,两者之比应是一个较小的常数。
2.1.6 希尔排序
- 希尔排序的思想是使数组中任意间隔为h的元素都是有序的。
- 算法中将h按照递增序列递减,通过插入排序实现h有序数组。
- 对于任意以1结尾的h序列, 都能将数组排序。
- 算法如下:
public class Shell{
public static void sort(Comparable[] a){
int N = a.length;
int h = 1;
while(h < N / 3)
h = 3 * h + 1;
while(h >= 1){
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;
}
}
- 希尔排序更高效的原因是它权衡了子数组的规模和有效性
- 排序之前各个子数组很短
- 排序之后子数组都是部分有序