理解希尔排序前,请务必先理解 插入排序,这是前提!
插入排序的思路就是在未排序的数组中选择第一个元素,在已经排序的数组中找到合适的位置并插入.他相比选择排序的优势在于,如果数组部分有序,或者大多数元素理最终的位置都不太远,就可以大大减少比较和交换次数.
但是存在这样情况,比如最小的元素恰好在数组的最末尾,它需要依次和相邻的元素比较和交换,所以我们需要进行需要N-1次比较和N-1次交换位置,才能将最小元素移动到数组的另一端.这个过程近乎选择排序,对与插入排序来说,有没有更好的办法来提高排序速度,减少这种移动呢?答案就是希尔排序
在插入排序的过程中,元素的比较和交换都是发生在相邻的元素中,而希尔排序可以一次跨越多个元素,在插入排序中,我们是跨度是1,在希尔排序中我们的跨度是h,实际上,希尔排序就是对插入排序的优化,在插入排序中我们说过插入排序的三种复杂度情况,并且插入排序适合部分元素有序,因为部分有序数组可以大大减少交换和比较的次数,那么希尔排序就是用来构建部分有序数组,从而为更小跨度的插入排序准备条件,以加快插入排序的速度
在插入排序中,元素的比较和交换的都是发生在相邻元素中的,操作跨度为 1,希尔排序的实际上就是不断的重复插入排序的过程,但是跨度从h开始,然后不断的递减,最后减小到1,当跨度为1时,这时候就是普通的插入排序了.
换句话说,希尔排序就是起点和跨度不同的插入排序,在每个跨度为h的子数组中,执行插入排序,并且h在不断的变小,变小的方式为 h=h/3:
当h=13时,将每个元素间隔为13的子数组进行插入排序,插入排序在所有间隔为13的子数组插入排序结束后,按照h=h/3
当h=4时, 将每个元素间隔为4的子数组进行插入排序,插入排序在所有间隔为4的子数组插入排序结束后,按照h=h/3
当h=1时,就是普通的插入排序了
之前我们说,当数组部分有序时,插入排序的优势时相当大的,而我们进行不同跨度的插入排序,就是为跨度为1的插入排序做准备.
当h减小到1时,其实数组已经是部分有序的了
插入排序的算法实现为:
public class ShellSort { public static void main(String[] args) { int[] nums = new int[]{1, 15, 3, 78, 34, 23, 46, 2, 8, 34, 57}; System.out.println(Arrays.toString(nums)); sort(nums); System.out.println(Arrays.toString(nums)); } public static void sort(int[] arrays) { int n = arrays.length; int h = 1; while (h < n / 3) h = 3 * h + 1; while (h >= 1) { for (int i = h; i < n; i++) { int j = i; for (; j >= h; j = j - h) { if (arrays[j] < arrays[j - h]){ int temp = arrays[j]; arrays[j] = arrays[j-h]; arrays[j -h] = temp; } } } h = h / 3; } } }
实际上上面这种写法的性能并不高,真正的希尔排序应该是下面这样的:
public static void sort(int[] arrays) { int n = arrays.length; int h = 1; while (h < n / 3) h = 3 * h + 1; while (h >= 1) { for (int i = h; i < n; i++) { for (int j = i; j >= h && arrays[j] < arrays[j - h]; j = j - h) { int temp = arrays[j]; arrays[j] = arrays[j - h]; arrays[j - h] = temp; } } h = h / 3; } }
将条件比较语句放在 for 循环内部!
希尔排序的过程为:
图中的高亮元素就是发生比较和交换操作的元素,可以对比下普通插入排序
到现在为止,还没有对选择排序,插入排序,希尔排序进行大规模的数据排序性能验证,下一篇,将使用十万,百万级别的数组进行排序性能检测