数据结构与算法系列——排序(4)_Shell希尔排序
1. 工作原理(定义)
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
希尔排序实质上是一种分组插入方法。它的基本思想是:对于n个待排序的数列,取一个小于n的整数gap(gap被称为步长)将待排序元素分成若干个组子序列,所有距离为gap的倍数的记录放在同一个组中;然后,对各组内的元素进行直接插入排序。 这一趟排序完成之后,每一个组的元素都是有序的。然后减小gap的值,并重复执行上述的分组和排序。重复这样的操作,当gap=1时,整个数列就是有序的。
2. 算法步骤
现在有一个序列:{h1,h2,… hn}:
1. 设先取定一个小于n的整数 di 作为一个增量,所有间隔为 di 的记录放在同一个子序列,然后在每个子序列内进行直接插入排序。
2. 然后取第二个增量d2(<d1),重复上述的分组和排序,直至所取的增量dt = 1 (d1>d2> … >dt-1>dt),即所有记录放在同一组中进行直接插入排序为止。
增量 di 的值是随便取的,但是这并不代表增量 di 的值可以随便取。也就是说,在定义增量 di 时,定义增量的序列为:dn>dn−1>...>d1=1,一般使用Shell建议的序列:di=n/2。
3. 图片演示
下面以数列{80,30,60,40,20,10,50,70}为例,演示它的希尔排序过程。
第1趟:(gap=4)
当gap=4时,意味着将数列分为4个组: {80,20},{30,10},{60,50},{40,70}。 对应数列: {80,30,60,40,20,10,50,70}
对这4个组分别进行排序,排序结果: {20,80},{10,30},{50,60},{40,70}。 对应数列: {20,10,50,40,80,30,60,70}
第2趟:(gap=2)
当gap=2时,意味着将数列分为2个组:{20,50,80,60}, {10,40,30,70}。 对应数列: {20,10,50,40,80,30,60,70}
注意:{20,50,80,60}实际上有两个有序的数列{20,80}和{50,60}组成。
{10,40,30,70}实际上有两个有序的数列{10,30}和{40,70}组成。
对这2个组分别进行排序,排序结果:{20,50,60,80}, {10,30,40,70}。 对应数列: {20,10,50,30,60,40,80,70}
第3趟:(gap=1)
当gap=1时,意味着将数列分为1个组:{20,10,50,30,60,40,80,70}
注意:{20,10,50,30,60,40,80,70}实际上有两个有序的数列{20,50,60,80}和{10,30,40,70}组成。
对这1个组分别进行排序,排序结果:{10,20,30,40,50,60,70,80}
4. 性能分析
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。
1. 时间复杂度
希尔排序的时间复杂度与增量(即,步长gap)的选取有关。例如,当增量为1时,希尔排序退化成了直接插入排序,此时的时间复杂度为O(N²),而Hibbard增量的希尔排序的时间复杂度为O(N3/2)。
希尔排序时间复杂度的下界是O(n*log2n)。
2. 空间复杂度
希尔排序过程中用到了直接插入排序,需要临时变量存储待排序元素,因此空间复杂度为O(1)。
3. 算法稳定性
希尔排序是不稳定的算法,对于相同的两个数,可能由于分在不同的组中而导致它们的顺序发生变化。
4. 初始顺序状态
- 比较次数:有关
- 移动次数:
- 复杂度: 有关
- 排序趟数:
4. 归位
不能归位,比如最后一个数为最小值,那么所有的值都未在最终的位置。
5. 优点
那么希尔排序算法为什么比直接插入排序好呢?
假设现在要对10个元素进行排序。
如果使用直接插入排序,大约花费的时间为 =10^2=100。
如果使用希尔排序,当增量di = 5时,分为5组,时间为5×2^2=20。当增量di = 2时,分为2组,时间为2×5^2=50。当di = 1时,分为1组,此时几乎是有序的,时间约为10,然后把每个分组的时间都加起来,总的时间约为20+50+10=80。
6. 具体代码
public int[] shellSort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int gap = arr.length/2;
while (gap > 0) {
for (int i = gap; i < arr.length; i++) {
int tmp = arr[i];
int j = i - gap;
while (j >= 0 && arr[j] > tmp) {
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = tmp;
}
gap = (int) Math.floor(gap / 2);
}
return arr;
}