希尔排序(Shell Sort),也称递减增量排序算法,通过将原始列表拆分为若干较小的子列表(其中每个子列表使用插入排序)进行排序。希尔排序是插入排序的一种更高效的改进版本。
算法描述
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
单次示例
以列表 [54, 26, 93, 17, 77, 31, 44, 55, 20]
为例,如果我们使用增量 3,则可以划分为三个子列表:
对每个子列表进行排序之后,我们会得到以下列表:
虽然这个列表并没有完全排序,但是已经很接近最终结果。就本例而言,再移 4 次就可以得到完全的排序列表:
如果我们使用增量 4,则可以划分为三个子列表,也可以进行子列表划分和排序:
完整描述
- 选定步长 gap(小于数据的总长度)。
- 按步长将数据分组。
- 对划分出来的每个组,分别进行插入排序。
- 重新选取步长 gap(小于上次的步长)并重复步骤 2~3,直到对最终步长 1 分组排序完毕。
步长序列
步长的选择是希尔排序的重要部分。步长从大到小递减,只要最终步长为 1(算法变为插入排序),任何步长序列都可以工作。
以下是几种不同的增量序列:
增量序列 | 增量公式 |
---|---|
原始 Shell 序列:n/2, n/4, …, 1 | $n/2^i$ |
Hibbard 序列:1, 3, 7, … | $2^k - 1$ |
Knuth 序列:1, 4, 13, … | $(2^k - 1)/2$ |
Sedgewick 序列:1, 5, 19, 41, 109, … | $9(4^k – 2^k) + 1$ 与 $2^{k+2} (2^{k+2} – 3) + 1$ 的并集 |
Python 实现
算法的 Python 代码如下(以原始 Shell 序列为例):
1 | def shell_sort(var): |
时间复杂度
位于 $O(n\log n)$ 和 $O(n^2)$ 之间,跟步长序列有关,以下列出其中两个常用序列的时间复杂度。
原始 Shell 序列:
- 平均时间复杂度:$O(n^2)$
- 最坏时间复杂度:$O(n^2)$
Hibbard 序列:
- 平均时间复杂度:$O(n^{3/2})$
- 最坏时间复杂度:$O(n^{3/2})$