Version: Next
希尔排序
Shell Sort
- 1959 年由 Donald Shell 提出
- 希尔排序将序列看成一个
矩阵
,分为m
列,进列
逐行排列
m
会从某个整数逐渐减为1
- 当
m = 1
,整个序列缩成一列,就完成了排序- 也被称为
递减增量排序
(Diminishing Increment Sort)- 矩阵的列数取决于
步长序列
step sequence
- 如果步长序列为 {1, 5, 19, 41, 109}
- 表示依次分为 109、41、19、5、1 列
- 不同的步长序列,执行效率不同
- 希尔本人给出的步长序列为
n / 2^k
,其中 n 为元素数目,k 为从1开始的正整数- 比如 n = 16,步长序列为 {1, 2, 4, 8}
问题
为什么不直接弄成一列排序
- 每一次排序,序列中的
逆序对
数目都会减少 - 因此,希尔排序底层一般使用插入排序对每一列进行排序
- 很多资料认为希尔排序是对插入排序的改进
实现
- 生成步长序列
- 遍历步长,逐列进行排序
- 要实现逐列,就是在原数组上,按
step
步长跳着走,只选中部分元素进行插入排序- 假设元素在第
col
列,第row
行,步长(总列数)是step
- 那么元素在数组中的索引时
col + row * step
(从0开始数)
col
:对 step 的遍历值- 初始位置为
col + step
然后与current - step
即前一个元素进行比较current
边界条件是大于
当前 列col
/**
* 希尔排序
*/
public class ShellSort<E extends Comparable<E>> extends Sort<E> {
public ShellSort(E[] array) {
this.array = array;
}
@Override
public void sort() {
List<Integer> stepSequence;
// 调用方法得到步长序列 例 {8,4,2,1} 按照希尔给出的写
stepSequence = shellStepSequence();
for (Integer step : stepSequence) {
shellSort(step); // 希尔排序
}
}
/**
* 分成 step 列进行排序
*
* @param step 步长序列中的一个成员,代表当前分成多少 "列" 进行排序
*/
private void shellSort(int step) {
for (int col = 0; col < step; col++) { // 逐列
insertSort(col, step); // 插入排序
}
}
private List<Integer> shellStepSequence() {
List<Integer> stepSequence = new ArrayList<>();
int division = this.array.length;
while ((division >>= 1) > 0)
stepSequence.add(division);
return stepSequence;
}
// 插入排序
private void insertSort(int col, int step) {
// col col+step col + 2step col + 3step ...
for (int begin = col + step; begin < array.length; begin += step) {
int current = begin;
while (current > col && cmp(current, current - step) < 0) {
swap(current, current - step);
current -= step;
}
}
}
public static void main(String[] args) {
Integer[] array = {4, 2, 3, 1};
ShellSort<Integer> integerShellSort = new ShellSort<>(array);
integerShellSort.sort();
for (Integer integer : array) {
System.out.println(integer + " ");
}
}
}
关于步长序列
- 希尔本人给出的步长序列,最坏时间复杂度是
O(n ^ 2)
- 目前已知最好的补偿序列,最坏时间复杂度是
O(n ^ (4/3))
,1986 年由 Robert Sedgewick 提出
值为 1
、5
、19
、41
、109
...