时间复杂度(运行时间):比较数量+交换数量(不交换元素的算法则计算访问数组的次数)
空间复杂度(额外内存使用)
1. 选择排序
“不断地选择剩余元素之中的最小者”:找到数组中最小的元素,将它和数组第一个元素交换位置。然后在剩下元素中找到最小的元素,将它与数组第二个元素交换位置。如此往复,直到将整个数组排序。
- 交换的总次数是N,算法的时间效率取决于比较的次数
-
选择排序需要大约N^2/2次比较(1+2+...+n-2+n-1)和N次交换
特点:
-
运行时间和输入无关
-
数据移动最少:交换次数和数组大小N是线性关系
//String、Integer已经实现了Comparable接口,自己就可以完成比较大小操作
public static void sort(Comparable[] a){
//将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 (a[j].compareTo(a[min])<0) min=j;
//交换i和最小元素
Comparable t = a[i];
a[i] = a[min];
a[min] = t;
}
}
2. 插入排序
“类似于扑克牌”:当前索引左边的所有元素都是有序的,但他们的最终位置还不确定,为了给更小的元素腾出空间,它们可能会被移动。担当索引达到数组的右端时,数组排序就完成了。
-
插入排序对部分有序数组很有效
-
算法的时间效率取决于输入中元素的初始顺序
-
情况 比较次数 交换次数 平均情况 N^2/4 N^2/4 最坏情况 N^2/2 N^2/2 最好情况 N-1 0 最坏情况:元素降序排列,比较次数为0+1+2...+n-1
最好情况:元素升序排列,比较次数为1*(N-1)
public static void sort(Comparable[] a){
//将a按升序排列
int N = a.length;
for (int i=1; i<N; i++){
//当前索引为i
//将a[i]插到a[i-1],a[i-2],a[i-3]...之中
for (int j=i; j>0&&(a[j].compareTo(a[j-1]))<0; j--){
//循环交换i 和 0~i-1之间比i小的数
Comparable t = a[j];
a[j] = a[j-1];
a[j-1] = t;
}
}
}
对于随机排序的无重复主键的数组,插入排序和选择排序的运行时间都是平方级别的
3. 希尔排序
“对插入排序的改进”:交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。思想是是数组中任意间隔为h的元素都是有序的(h有序数组),对h个子数组内部进行插入排序。
-
与选择排序和插入排序不同的是,希尔排序也可以用于大型数组。
-
希尔排序比选择排序、插入排序快很多,而且数组越大优势越大。
-
希尔排序的运行时间达不到平方级,最坏情况下比较次数和N^(3/2)成正比。
-
使用递增序列1,4,13,40,121,364......的希尔排序所需比较次数不会超过N的若干倍乘递增序列长度
public static void sort(Comparable[] a){
//将a[]按升序排列
int N = a.length;
int h = 1;
while (h<N/3) h=h*3+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&&(a[j].compareTo(a[j-h]))<0; j-=h){
Comparable t = a[j];
a[j] = a[j-h];
a[j-h] = t;
}
}
h = h/3;
}
}