1前言
排序算法的目的就是将所有的元素的主键按照某种方式排列(通常是从小到大或者字母顺序)。
排序后索引较大的主键大于等于索引较小的主键。
元素和主键的具体性质在不同的应用中千差万别,在java
中,元素通常都是对象,对主键的抽象描述则需要通过一种内置机制(Comparable接口)来完成。
因此,需要进行排序的元素(对象)必须实现了Comparable接口
。
2排序算法的基本模板
/**
* 所有排序算法的抽象父类,规定了具体排序算法的格式。
* @Author: AZhu
* @Date: 2021/2/10 22:15
*/
public abstract class Sorting {
/**
* 核心排序算法,对数组a[]进行排序。
* @param a 目标数组
*/
public static void sort(Comparable[] a){
}
/**
* 比较v和w的大小
* @param v 元素v
* @param w 元素w
* @return v是否小于w
*/
public static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
/**
* 在数组a[]中,将索引i和j中的元素调换位置。
* @param a 数组
* @param i 索引i
* @param j 索引j
*/
public static void exch(Comparable[] a, int i, int j) {
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
/**
* 输出数组a[]的内容
* @param a 数组
*/
public static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
System.out.println(a[i] + " ");
}
}
/**
* 检测数组a[]是否有序
* @param a 数组
* @return a[]是否有序
*/
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;
}
}
3选择排序
首先,找到数组中最小的元素,将它和数组的第1
个元素交换位置。
其次,在右边剩下的元素中找到最小的元素,将它和数组的第2
个元素交换位置。
如此往复,直到将整个数组排序。
- 本质:在不停地选择剩余元素中的最小者。
实现:
/**
* 选择排序
* @Author: AZhu
* @Date: 2021/2/10 22:34
*/
public abstract class Selection extends Sorting {
public static void sort(Comparable[] a){
int N = a.length;
for (int i = 0; i < N; i++) {
//将a[i]与a[i+1...N]中的最小元素交换
int min = i;//最小元素的索引
for (int j = i + 1; j < N; j++) {
if (less(a[j], a[min])) {
min = j;
}
}
exch(a, i, min);
}
}
}
分析:
- 运行时间与输入无关:一个有序和一个无序的数组所花费的时间是一样的。
- 数据的移动是最少的。
- 对于长度为N的数组,选择排序需要大约
N^2/2
次比较和N
次交换。
4插入排序
将后面无序的每一个元素,插入到前面已经有序的元素中的合适位置。
为了给要插入的元素腾出位置,我们需要将其余所有元素在插入之前都向右移动一位。
实现:
/**
* 插入排序
* @Author: AZhu
* @Date: 2021/2/10 23:09
*/
public class Insertion extends Sorting {
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);
}
}
}
}
分析:
- 插入排序所需要的时间取决于输入中元素的初始顺序。
- 对于随机排列的长度为
N
且主键不重复
的数组:- 平均情况下插入排序需要
~ N^2/4
次比较以及~ N^2/4
次交换。 - 最坏情况下需要
~ N^2/2
次比较以及~ N^2/2
次交换。 - 最好情况下需要
N-1
次比较以及0
次交换。
- 平均情况下插入排序需要
- 插入排序对部分有序的数组十分高效,也很适合小规模的数组。
5比较两个算法的程序
/**
* 比较两种排序算法的类
* @Author: AZhu
* @Date: 2021/2/11 10:48
*/
public class SortCompare {
/**
* 返回排序算法的运行时间
* @param alg 排序算法的名称
* @param a 数组
* @return 排序时间
*/
public static double time(String alg, Double[] a) {
Stopwatch timer = new Stopwatch();
if (alg.equals("Insertion")) Insertion.sort(a);
if (alg.equals("Selection")) Selection.sort(a);
if (alg.equals("Shell")) ;
if (alg.equals("Merge")) ;
if (alg.equals("Quick")) ;
if (alg.equals("Heap")) ;
//返回排序的运行时间
return timer.elapsedTime();
}
/**
* 使用算法alg将T个长度为N的数组排序
* @param alg 算法名称
* @param N 数组长度
* @param T 数组个数
* @return 总运行时间
*/
public static double timeRandomInput(String alg, int N, int T) {
double total = 0.0;
Double[] a = new Double[N];
for (int t = 0; t < T; t++) {
//进行一次测试(生成一个数组并排序)
for (int i = 0; i < N; i++) {
a[i] = StdRandom.random();
//System.out.println(a[i]);
}
total += time(alg, a);
}
return total;
}
public static void main(String[] args) {
String als01 = "Selection";
String als02 = "Insertion";
int T = 100;
int N = 1000;
double t1 = timeRandomInput(als01, N, T);//算法01的总时间
double t2 = timeRandomInput(als02, N, T);//算法02的总时间
System.out.println("对于 " + T + " 个长度为 " + N + " 的随机数组:");
System.out.println("算法:" + als01 + " 的总运行时间为" + t1 + "秒。");
System.out.println("算法:" + als02 + " 的总运行时间为" + t2 + "秒。");
}
}
6希尔排序
- 希尔排序是一种基于插入排序的快速的排序算法。
- 希尔排序为了加快速度简单地改进了插入排序,
- 交换不相邻的元素以对数组的局部进行排序
- 最终用插入排序将局部有序的数组排序
实现:
/**
* 希尔排序
* @Author: AZhu
* @Date: 2021/2/11 11:49
*/
public class Shell extends Sorting {
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,1093,...
while (h >= 1) {
//将数组变为h有序
for (int i = h; i < N; i++) {
//将a[i]插入到a[i-h] a[i-2*h] a[i-3*h] ...之中
for (int j = i; j >= h && less(a[j], a[j - 1]); j--) {
exch(a, j, j - h);
}
}
h = h / 3;
}
}
}
分析:
以上使用的递增序列的计算和使用都很简单,和复杂的递增序列的性能接近。
希尔排序也可以用于大型数组,它对任意排序的的数组表现也很好。
以上的希尔排序的运行时间达不到平方级别。
在最坏的情况下的比较次数和N^(3/2)
成正比。
使用上述的递增序列的希尔排序所需的比较次数不会超过N
的若干倍乘以递增序列的长度。
代码量少,不需要额外的内存空间,因此对于中等大小的数组也可以使用希尔排序。