引入
希尔排序又称“缩小增量排序”,是插入排序的一种。
直接插入排序当待排序列的记录个数较少且待排序列的关键字基本有序时,效率较高。希尔排序基于以上两点,从“减少记录个数”和“序列基本有序(每趟排序都会使序列更加有序)”里两个方面对直接插入排序进行改进。
基本思想
希尔排序实质上是采用分组插入的方法。先将整个待排序列分割成几组,从而减少参与直接插入排序的数据量,对每组分别进行直接插入排序,然后重新分组,增加每组的数据量。这样经过几次分组排序后,整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。
希尔对元素的分割,不是简单的“逐段分割”,而是将相隔某个“增量”的记录分成一组。
- 第一趟取增量d1(d1<n),把全部记录分成d1个组,所有间隔为d1的记录分在同一组,在各个组中进行直接插入排序。
- 第二趟取增量d2(d2<d1),重复上述的分组和操作。
- 以此类推,直到所取得增量dt=1,所有记录在同一组中进行直接插入排序为止。
算法性能分析
1、时间复杂度
希尔排序的时间复杂度取决于增量数组的选取。增量数组选取规则主要有两种。
- 希尔自己提出的:
Dt=n/2t向下取整 ,且最后一个增量为1(t为趟次)。此时的时间按复杂度为 - 帕佩尔诺夫和斯塔舍维奇提出的:
2k+1…65,33,17,9,5,3,1其中,k为大于等于1的整数,2k+1小于待排序列的长度,末尾的1是额外添加的。 此时的时间复杂度为。当n在某个特定范围内,希尔排序所需要的比较和移动次数约为,时间复杂度为。当n趋于无穷时,可减少到,但最坏情况下之间复杂度为。
2、 空间复杂度
等同于直接插入和折半插入。为。
算法特点
- 稳定性:当相同元素被划分到不同子表时,可能会改变他们之间的相对次序,是一种不稳定的排序
- 适用性:只适用于顺序存储结构
- 增量序列有多种取法。但都应该满足两点要求:(1)序列中的每个值都没有除1之外的公因子;(2)最后一个值为1
- 记录总的比较次数和移动次数都比直接插入要少。n越大时效果越明显,所以希尔排序适合初始序列无序,n比较大时的情况。
代码
import java.util.Arrays;
import Utils.Swap;
public class 希尔排序 {
static int[] arr = { 1, 5, 3, 2, -7, 8, 0, 9, 4, 6 };
static int len = 10;
static void ShellSort1() {
int gap = 1, i, j;
int temp;
while (gap < len / 3)
gap = gap * 3 + 1; // <O(n^(3/2)) by Knuth,1973>: 1, 4, 13, 40, 121, ...
for (; gap > 0; gap /= 3) {
for (i = gap; i < len; i++) {
temp = arr[i];
for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap)
arr[j + gap] = arr[j];
arr[j + gap] = temp;
}
}
}
static void ShellSort2() {
int gap = len / 2;
for (; gap > 0; gap /= 2) { // 不断缩小gap,直到1为止
//System.out.println("Gap=" + gap);
for (int j = 0; (j + gap) < len; j++) { // 使用当前gap进行组内插入排序
for (int k = 0; (k + gap) < len; k += gap) {
//System.out.println("Compare: arr[" + (k+gap)+ "]=" + arr[k+gap] + ", arr[" + k + "]=" + arr[k]);
if (arr[k] > arr[k + gap]) {
Swap.swap(arr, k + gap, k);
// System.out.println(" Sorting: " + Arrays.toString(arr));
}
}
}
}
}
public static void main(String[] args) {
//ShellSort1();
ShellSort2();
System.out.println(Arrays.toString(arr));
}
}