在上一节,我们聊了一下插入排序算法,这种算法每次比较,都需要移动元素,给待插入数据腾位置,如果我们大部分的数据都是有序的,只有一个数据无序且最小并处于数组末端,我们移动这个小元素前面的数据其实没有任何意义。由此,诞生了希尔排序(也叫递减增量排序),希尔排序就是为了解决插入排序大量移动元素的问题。
希尔排序的思路是:初始化一个gap变量,每次跳跃gap步长进行元素比较。一轮比较之后,gap下标左边的元素和gap右边的元素大体上都有序了。我们举个例子:
8,7,2,9,6,1
第一轮比较,gap = (array.length / 2),gap = 3。我们需要比较的元素是8和9、7和6、2和1。比较后的数组演化为:
8,6,1,9,7,2。
之后,gap从现有基础上再次缩小2,gap = 3/2 ,gap = 1。这一轮比较,因为步长为1,所以相当于是对整个数组进行排序。我们需要比较的元素是8、6、1、9、7、2。
gap为1时,希尔排序停止。
我在网上找了一个动图,看着挺直观的,大家可以看一下。动图来源:https://www.cnblogs.com/fivestudy/p/10064969.html
java实现如下:
public static void main(String[] args) {
int[] arr = {8, 7, 6, 5, 4, 3, 2, 1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
/**
* 希尔排序 针对有序序列在插入时采用交换法
*
* @param arr
*/
public static void sort(int[] arr) {
int n = arr.length;
//增量gap,并逐步缩小增量
for (int gap = n / 2; gap > 0; gap /= 2) {
//从第gap个元素,逐个对其所在组进行直接插入排序操作
for (int i = gap; i < arr.length; i++) {
int j = i;
while (j - gap >= 0 && arr[j] < arr[j - gap]) {
//插入排序采用交换法
swap(arr, j, j - gap);
//j递减gap,是和本组中的其他元素进行比较。
j -= gap;
}
}
}
}
/**
* 交换数组元素
*
* @param arr
* @param a
* @param b
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
希尔排序的关键逻辑是gap的选择,希尔本人给出的增量选择方法是:Array.length / 2,之后依次递减2即可。但是这种算法其实不是最优解,据说增量的选择是当前的一个数学难题,有科学家搞出了更优解,大家有兴趣自己查查吧。
时间复杂度分析
希尔排序时间复杂度是 O(n^(1.3-2))。比普通的插入排序快的多,但是数据量大的情况下,没有快速排序好。中等规模的数据,希尔排序的性能还可以。
空间复杂度分析
没有额外空间使用,空间复杂度为O(1)
原地排序算法分析
空间复杂度为O(1),所以是原地排序算法。
稳定排序算法分析
希尔排序是非稳定排序算法,看执行过程,很容易就能看出来。