选择排序
选择排序:首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。再次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。
public int[] selectSorted(int[] a) {
int length = a.length;
int temp;
int min;
for (int i = 0; i < length; i++) {
min = i;
for (int j = i + 1; j < length; j++) {
if (a[min] > a[j]) {
min = j; // 改变最小项的索引
}
}
// 交换位置
temp = a[i];
a[i] = a[min];
a[min] = temp;
}
return a;
}
选择排序的性质:
对于长度为 N 的数组,选择排序需要大约
N
2
2
\frac{N^2}{2}
2N2次比较和 N 次交换。
证明:0 到 N-1 的任意 i 都会进行一次交换和
N
−
1
−
i
N-1-i
N−1−i 次比较,因此总共有 N
次交换以及
(
N
−
1
)
+
(
N
−
2
)
+
.
.
.
+
2
+
1
=
N
(
N
−
1
)
2
~
N
2
2
(N-1)+(N-2)+...+2+1=\frac{N(N-1)}{2} ~ \frac{N^2}{2}
(N−1)+(N−2)+...+2+1=2N(N−1)~2N2次比较
运行时间和输入无关。
弊端:为了找出最小的元素而扫描一遍数组并不能为下一遍扫描提供什么信息。一个已经有序的数组或是主键全部相等的数组和一个元素随机排列的数组所用的排序时间是等长的。
数据移动是最少的。
每次交换都会改变两个数组元素的值,因此选择排序用了 N 次交换——交换次数和数组的大小是线性关系。其他任何算法都不具备这个特征,大部分的增长数
量级都是线性对数或是平方级别。
插入排序
插入排序:将每一项元素插入到其他已经有序的序列中的适当位置。为了给要插入的元素腾出空间,需要将其余所有元素在插入之前都向右移动一位
public static int[] insertSorted(int[] a) {
int length = a.length;
int temp;
for (int i = 1; i < length; i++) {
for (int j = i; j > 0 && a[j - 1] > a[j]; j--) {
temp = a[j - 1];
a[j - 1] = a[j];
a[j] = temp;
}
}
return a;
}
插入排序的性质
对于随机排列的长度为 N 且主键不重复的数组,平均情况下插入排序需要 N 2 4 \frac{N^2}{4} 4N2次比较以及 N 2 4 \frac{N^2}{4} 4N2次交换。最坏情况下需要 N 2 2 \frac{N^2}{2} 2N2次比较和 N 2 2 \frac{N^2}{2} 2N2 次交换,最好情况下需要 N − 1 N-1 N−1次比较和 0 次交换。
插入排序所需的时间取决于输入中元素的初始顺序
因此插入排序算法非常适合进行对部分有序数组的排列
对上面的排序算法的优化:可以在内循环中将较大的元素都向右移动而不总是交换
两个元素,这样访问数组的次数就能减半
public static int[] insertSorted(int[] a) {
int length = a.length;
int temp;
int j;
for (int i = 1; i < length; i++) {
temp = a[i];
for (j = i; j > 0 && temp < a[j - 1]; j--) {
a[j] = a[j - 1];
}
a[j] = temp;
}
return a;
}
对于随机排序的无重复主键的数组,插入排序和选择排序的运行时间是平方级别的,两者之比应该是一个较小的常数。
希尔排序
希尔排序是对插入排序的改进。对于大规模乱序数组插入排序很慢,因为它只会交换相邻的元素,因此元素只能一点一点地从数组的一端移动到另一端。例如,如果主键最小的元素正好在数组的尽头,要将它挪到正确的位置就需要 N-1 次移动。希尔排序为了加快速度简单地改进了插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。
希尔排序的思想是使数组中任意间隔为 h 的元素都是有序的。这样的数组被称为 h 有序数组。换句话说,一个 h 有序数组就是 h 个互相独立的有序数组编织在一起组成的一个数组。在进行排序时,如果 h 很大,我们就能将元素移动到很远的地方,为实现更小的 h 有序创造方便。用这种方式,对于任意以 1 结尾的 h 序列,都能够将数组排序。这就是希尔排序。
public static int[] shellSorted(int[] a) {
int length = a.length;
int h = 1;
int base = 3;
int i, j;
int temp;
while (h < length / base) {
h = h * base + 1;
}
while (h >= 1) {
for (int i = h; i < length; i++) {
temp = a[i];
for (j = i; j >= h && temp < a[j - h]; j -= h) {
a[j] = a[j - h];
}
a[j] = temp;
}
h /= base;
}
return a;
}
希尔算法的性能取决于递增序列的好坏。确定如何选择递增序列是十分困难,因为算法的性能不仅取决于 h,还取决于 h 之间的数学性质,比如它们的公因子等。有很多论文研究了各种不同的递增序列,但都无法证明某个序列是“最好的”。
和选择排序以及插入排序形成对比的是,希尔排序也可以用于大型数组。它对任意排序(不一定是随机的)的数组表现也很好。
其时间复杂度为 O ( N 1.3 ) O(N^{1.3}) O(N1.3)- O ( N 2 ) O(N^2) O(N2)
使用递增序列 1, 4, 13, 40, 121, 364 ( a n = a n − 1 ∗ 3 + 1 a_n = a_{n-1} * 3 + 1 an=an−1∗3+1)…的希尔排序所需的比较次数不会超出 N 的若干倍乘以递增序列的长度。