我们关注的主要对象是重新排列数组元素的算法,其中每一个元素都有一个主键.排序算法的目标是将所有的主键按某种方式排列.排序后索引较大的元素大于等于索引较小的元素主键.
在java中,元素通常是对象,对主键的抽象描述则是通过一种内置的机制来完成的.
下面是排序算法类模版
import edu.princeton.cs.algs4.In; import edu.princeton.cs.algs4.StdOut; public class Example { //排序 public static void sort(Comparable[] a){ } //比较v和w的大小 private static boolean less(Comparable v, Comparable w){ return v.compareTo(w) < 0; } //交换索引为i和j的元素 private static void exch(Comparable[] a, int i, int j){ Comparable t = a[i]; a[i] = a[j]; a[j] = t; } //打印数组 private 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; } return true; } public static void main(String[] args) { String[] a = In.readStrings(); sort(a); assert isSorted(a); show(a); } }
我们实现CompareTo接口保证用例代码将其排序.要做到这一点,只要实现一个CompareTo()方法来定义目标类型对象的自然次序.
CompareTo()实现了主键抽象——它给出了CompareTo接口的任意数据类型的对象的大小顺序定义.需要注意的是CompareTo()方法不一定会用到进行比较的实例的所有变量,毕竟数组元素的主键很可能只是元素很小的一部分.
选择排序
首先,找到数组中最小的元素,其次,将它和数组的第一个元素交换位置(如果第一个就是最小则和它自己交换).
再次,在剩下的元素中找到最小的元素,将它和数组的第二个元素交换位置.
如此反复,直到将整个数组排序.这种方法叫选择排序,因为它在不断选择剩余元素之中的最小者.
排序轨迹如图
//选择排序 public static void SelectSort(Comparable[] a){ int N = a.length; int min; for(int i = 0; i < N; i++){ min = i; for(int j = i; j < N; j++){ if(less(a[j], a[min])) min = j; } exch(a,i,min); } }
对于长度为N的数组,大约需要进行N^2/2次比较和N次交换.
特点
1.运行时间和输入无关.
2.数据移动是最少的.
插入排序
将一个数插入已经排好序的另一组数中的适当位置,为了给元素腾出空间,我们可能需要将其余所有元素在插入之前都向右移动一位,这种算法叫做插入排序.
与选择排序不同的是,插入排序所需时间取决于输入中元素的初始顺序,例如,对一个很大且其中的元素已经有序(或接近有序)的数组进行排序将会比对随机顺序的数组或逆序的数组进行排序要快得多.
算法轨迹
public static void InsertSort(Comparable[] a){ int N = a.length; Comparable key; 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); } } }
这里其实可以对它进行一下优化:在插入排序的内循环中,使用二分法查找出所有较大元素,然后全部右移,而不是总是交换两个元素,这样访问数组的次数就可以减半.
public static void BinaryInsertSort(Comparable[] a){ int N = a.length; for (int i = 1; i < N; i++) { Comparable v = a[i]; int lo = 0, hi = i; //二分查找 while (lo < hi) { int mid = lo + (hi - lo) / 2; if (less(v, a[mid])) hi = mid; else lo = mid + 1; } //元素后移 for (int j = i; j > lo; j--) a[j] = a[j-1]; a[lo] = v; } }
希尔排序
希尔排序的思想是使得数组中间任意间隔为h的元素都是有序的,这样的数组称为h有序数组,也就是说,一个h有序数组就是h个互相独立的有序数组编织在一起组成员一个数组.
实现希尔排序的一种方法是对于每个h,用插入排序将h个子数组独立地排序,但因为子数组是相互独立的,一个更简单的办法是在h-子数组将每个元素交换到比它大的元素之前去,只需要在插入排序中将移动元素的距离由1改成h即可.
排序轨迹如图
public static void ShellSort(Comparable[] a){ int N = a.length; int h = 1; //步长选择 while(h<N/3) h = 3*h + 1; //1,4,13,40,121,364,1093... while(h>=1){ //数组变成h有序 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; } }
算法的实现使用了序列1/2(3^k-1),从N/3开始递减至1,这个序列称为递增序列,另一种方式是将递增序列存储在一个数组中.
当一个"h有序"的数组按照增幅k排序之后,它仍然是"h”有序的.