希尔排序
介绍
希尔排序(Shell Sort)是一种改进的插入排序算法,通过比较较远距离的元素来工作,其核心理念是使数组中任意间隔为 gap
的元素都是有序的,通过不断减小 gap
的值,最终使得整个数组变得有序。
因为希尔排序是由插入排序改进而来,因此建议在学习希尔排序之前先了解插入排序。
大家可以看看孤诣写的插入排序的介绍文章:排序算法 - 插入排序
原理
希尔排序的原理是通过定义一个间隔序列来分组,原始数据被分为多个子序列,每个子序列的元素在原数组中相隔一定长度。在每个子序列内进行插入排序后,整体数据的有序度会提高。随着间隔序列的逐渐减小,最终减至1,整个数据序列变为基本有序,这时再进行一次插入排序即可完成整个排序过程。
在希尔排序中,间隔序列的选择非常关键,它影响着算法的性能。常用的间隔序列有:
N/2, N/4, ..., 1
(其中N
是数组的长度)。
这里我们通过对一个长度为 10 的数组进行排序,以此来演示希尔排序的原理。
数组的初始状态设置为 [5, 0, 3, 9, 4, 1, 7, 6, 2, 8]
。
第一轮
我们以 5(数组长度的一半) 做为 gap
(间隔数),可以将数组划分为 5 个分区:[5, 1], [0, 7], [3, 6], [9, 2], [4, 8]
5 和 1 比较,5 > 1,5 和 1 位置互换:
0 和 7 比较,0 < 7,0 和 7 位置不变:
…
第一轮处理过后,数组元素的顺序变为 [1, 0, 3, 2, 4, 5, 7, 6, 9, 8]
。
第一轮排序的完整过程请看下面的动图:
第二轮
我们以 2(上一轮间隔数的一半) 做为 gap
(间隔数),可以将数组划分为 2 分区:[1, 3, 4, 7, 9], [0, 2, 5, 6, 8]
。
因为第一组和第二组的元素都是有序的,所以数组元素的顺序不会发生改变,还是 [1, 0, 3, 2, 4, 5, 7, 6, 9, 8]
。
这里直接上过程的演示动图:
第三轮
我们以 1(上一轮间隔数的一半) 做为 gap
(间隔数),即对数组整体进行排序。
在 gap=1
时,将对数组整体进行插入排序,过程如下:
1 和 0 比较,1 > 0,1 和 0 位置互换:
1 和 3 比较,1 < 3,1 和 3 位置不变:
3 和 2 比较,3 < 2,3 和 2 位置互换:
3 和 4 比较,3 < 4,3 和 4 位置不变:
…
因为本轮的 gap=1
, 所以此轮处理过后,数据变为有序数组 [1, 2, 3, 4, 5, 6, 7, 8, 9]
。
完整的排序过程请看下面的动图:
代码
希尔排序的 Java 实现如下:
/**
* 希尔排序
*
* @author 孤诣
*/
public class ShellSort {
public static void main(String[] args) {
// 数据准备
int[] array = {5, 0, 3, 9, 4, 1, 7, 6, 2, 8};
System.out.println(Arrays.toString(array));
// 利用希尔排序对数据进行排序
shellSort(array);
System.out.println(Arrays.toString(array));
}
/**
* 希尔排序算法的实现
*
* @param source 源数组
*/
public static void shellSort(int[] source) {
if (source == null) {
return;
}
// 初始增量为数组长度的一半
int dataNum = source.length;
int gapFactor = 2;
for (int gap = dataNum / gapFactor; gap > 0; gap /= gapFactor) {
// 从第 gap 个元素开始,逐个对其所在的组进行直接插入排序操作,不同组的排序会交替进行
for (int i = gap; i < dataNum; i++) {
int current = source[i];
int j = i;
// 寻找合适的插入位置
while (j - gap >= 0 && current < source[j - gap]) {
source[i] = source[j - gap];
j -= gap;
}
// 插入合适的位置
source[j] = current;
}
}
}
}
评估
**时间复杂度:**希尔排序的时间复杂度与增量序列的选择有关。
- 平均时间复杂度:
O(nlogn)
到O(n^1.5)
。 - 当增量为 1 时,将退化为插入排序,时间复杂度为
O(n^2)
。
**空间复杂度:**希尔排序是原地排序算法,除了输入数组之外只需要常数级别的辅助空间,因此空间复杂度为O(1)
。
**稳定性:**希尔排序是不稳定的排序算法,因为在不同的步长排序中,相同元素的顺序可能会改变。
总结
希尔排序通过预排序将较大范围的记录变得基本有序,从而减少了直接插入排序中记录的比较次数和移动次数。
希尔排序在较大数组中表现良好,比传统的插入排序要快很多,并且没有快速排序和归并排序那样的递归开销;但是,与其他更高效的排序算法相比,希尔排序的性能较差,比如:如快速排序、归并排序等。
综合来看,希尔排序适用于中等大小的数组,对于小型数组,插入排序可能更有效;对于大型数组,快速排序、归并排序可能是更好的选择。
对快速排序算法和插入排序算法不了解的小伙伴可以看看孤诣的这两篇文章:
- 快速排序:http://t.csdnimg.cn/4jWYO
- 插入排序:http://t.csdnimg.cn/z5cGV
对于归并排序算法,后续孤诣也会专门写一篇文章为大家介绍。