主要介绍两种基本的排序算法:选择排序、插入排序以及一种基于插入排序算法的变体——希尔排序。
一、选择排序
算法流程:
1、找到数组中最小的元素;
2、将该元素与数组的第一个元素交换(如果第一个元素最小则它和自己交换);
3、在剩余元素中找到最小元素,将它与数组中第二个元素交换位置;
4、重复2和3,直到整个数组有序为止。
算法效率:
该算法主要由两层循环执行,外循环从0~N(数组长度),内循环从已经有序的最后元素的后一个元素i~N。交换元素的代码写在内循环之外,每次交换都能确定一个元素的位置(确定后元素位置不再改变),总的交换次数为N,因此,算法的时间效率取决于比较的次数。
通过分析可知,对于0~N-1的任意元素i 都会进行一次交换和N-i-1次比较,因此,总的比较次数为:
(N-1)+(N-2)+(N-3)+...+2+1 = N(N-1)/2 ~ N²/2次比较
选择排序有两个很鲜明的特点:
1、运行时间与输入序列无关。使用选择排序对长度相同的已经有序的数组和元素完全随机排列的数组所用的时间是一样的。
2、元素移动次数最少。由前面的分析可知,元素交换的次数与数组长度是线性关系。
代码示例:
// 选择排序
static int[] selectSort(int[] arr){
int N = arr.length;
for (int i = 0; i < N; i++) {
int min = i;
for (int j = i+1; j < N; j++) {
if (arr[j] < arr[min]) {
min = j;
}
}
// 内层循环结束后,找出最小元素,交换
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
return arr;
}
二、插入排序
通常,人们打扑克时,整理牌的方法即类似于插入排序。与选择排序一样,当前索引左边的元素都是有序的,但他们的最终位置还不确定。但是,当索引到达数组右端时,排序就完成了。
与选择排序不同的是,插入排序的时间取决于输入序列的初始顺序,对于元素有序(或接近有序)的数组比元素随机排列的数组的排序时间要小得多。
对于随机排列的长度为N且元素不重复的数组:
平均情况下插入排序需要~N²/4次比较以及~N²/4次交换;
最坏的情况下需要~N²/2次比较和~N²/2次交换;
最好情况下需要N-1次比较和0次交换(输入序列 已经有序)。
代码示例:
// 插入排序
static int[] insertSort(int[] arr){
for (int i = 1; i < arr.length; i++) {
for (int j = i; j > 0 && arr[j] < arr[j-1]; j--) {
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
}
}
return arr;
}
三、希尔排序
希尔排序是一种基于插入排序的快速排序算法。使用插入排序对大规模的乱序数组进行排序,由于每次只交换相邻的元素,因此元素只能一点一点地从数组的一端移动到另一端。
希尔排序对插入排序进行了简单的改进:交换不相邻的元素以对数组的局部进行排序并最终用插入排序将局部有序的数组进行排序。其基本思想是使得数组中任意间隔为h的元素都有序(称作h有序)。这样,在进行排序时,如果h的值很大,我们仅用一次就能将元素移动到很远的地方,为后续的更小的h有序创造有利条件。
一种简单的希尔排序实现方法是在插入排序中加入一个外循环是的h的值按照一定的递增序列递减,并将代码中元素移动的距离由1改为h即可:
// 希尔排序
static int[] shellSort(int[] arr){
int n = arr.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 && arr[j] < arr[j - h]; j -= h) {
int temp = arr[j];
arr[j] = arr[j - h];
arr[j - h] = temp;
}
}
h = h / 3;
}
return arr;
}
希尔排序算法的性能取决于h递增序列的选择以及h之间的数学性质(如他们之间的公因子等),如何选择这个递减序列不在本文的讨论范围之内,很多论文研究了各种不同的递增序列,但都无法证明某个序列是“最好的”。
与前两种排序算法形成对比的是,希尔排序可以应用于大型数组,它对任意排序的数组表现也很好。而且,数组越大,其相对于选择排序和插入排序的优势就更大。
《算法》(第四版)中关于希尔排序有如下结论:
使用h递增序列1,4,13,40,121,364,...的希尔排序所需的比较次数不会超出N(数组长度)的若干倍乘以递增序列的长度。