希尔排序(Shell's Sort)是插入排序的一种,又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
排序步骤:
(这里取增量(步长)为最大(序列长度),每趟排序增量除2,直到等于1为止)
- 先设置一个步长(步长设置为序列长度)
第一趟排序
0、先步长除2
1、从第一个元素开始,每隔一个步长取一个元素,将这些元素分为一组
2、然后这一组再进行直接插入排序
(直接插入排序:将序列分为两组,前面有序,后面无序,每次取后面的元素,插入到前面有序序列中的合适位置)
重复上述 1、 2 步操作
1、再从第二个元素开始,每隔一个步长取一个元素,将这些元素分为一组
2、然后这一组再进行直接插入排序
......
(其实,每次都有步长那么多组,假如步长为5,就分5组,重复5次;步长为2,就分2组,重复2次)
第二趟排序
- 重复上述操作
......
当步长缩小到1,进行整个序列的直接插入排序,然后结束排序
(如果不懂直接插入排序,请参考这篇https://blog.csdn.net/weixin_42193813/article/details/105033723)
举例说明
比如一组序列
- 长度 = 10
- 所以先设置步长 = 10
第一趟排序
- 步长除2 = 5
- 共分步长那么多组:5组
- 如下:8和3分为一组,9和5分为一组......
- 然后我们分别对每一组进行直接插入排序
得出结果如下,然后对结果再进行第二趟排序
第二趟排序
- 步长除2 = 2
- 共分步长那么多组:2组
- 如下:3、1、0、9、7分为一组,5、6、8、4、2分为一组
- 然后我们分别对每一组进行直接插入排序
得出结果如下,然后对结果再进行第三趟排序
第三趟排序
- 步长除2 = 1
- 共分步长那么多组:1组
- 如下:所有分为一组
- 然后我们对这一组进行直接插入排序
得出结果如下,此时为一个有序序列
Java代码
public static void shellSort(int[] array) {
// 当只有一个元素时,直接返回
if (array.length == 1) {
return;
}
int gap = array.length; // 步长
while (true) {
gap = gap / 2; // 每次步长除2
// 每次分 步长 那么多组,然后每组分别进行插入排序
for (int i = 0 ; i < gap ; i++) {
// 下面其实就是插入排序
for (int j = i + gap; j < array.length ; j += gap) {
int k;
for (k = j ; k > 0 ; k--) {
if (array[k - 1] > array[k]) {
int temp = array[k];
array[k] = array[k - 1];
array[k - 1] = temp;
}
}
}
}
// 当步长为1时结束
if (gap == 1) {
break;
}
}
}
可以将它简化为三层循环,换一种写法,但是基本思想都是一样的
(如果你理解了直接插入排序,那么上述代码应该是看起来很直观了)
时间复杂度
希尔排序的时间复杂度与增量(步长)的选取有关。
例如,当增量为1时,希尔排序退化成了直接插入排序,此时的时间复杂度为O(N²)
当增量 = (3x + 1) 时,时间复杂度为
- 希尔算法在最坏的情况下和平均情况下执行效率相差不是很多
- 快速排序在最坏的情况下执行的效率会非常差。
专家们提倡,几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法