希尔排序也叫缩小增量排序,是对插入排序的一种改进,同时该算法也是冲破O()的第一批算法之一。插入排序的思路是每次挑选一个数值,并把它插入到数列的合适位置,具体算法实现可见我之前的博客基础算法——插入排序-优快云博客,通过分析可以看到,插入排序每次只能为一个数值确定位置,效率比较低,所以在直接插入排序的基础上,进行了升级优化,就有了希尔排序。
插入排序的操作对象是整个数列,每一轮排序只能确定一个数的位置,而且有时还要遍历整个数列寻找合适数值,数列短的话还好,如果在数列很长,最小值又在最末端的情况下,就需要逐个往前比较,直到第一个位置,效率很低,所以对于顺序完全颠倒的数列来说,插入排序就比较慢,而对于大致有序的数列,插入排序效率就很快了。
因此需要对插入排序进行适当优化,我们可以先将数列进行一个简单的预排序,让数列变得比较有序,然后再充分发挥插入排序的优势,对这个大致有序的数列进行排序,最终完成排序。
这就是希尔排序的基本原理,它基于插入排序,又做了一定的优化。
希尔排序的原理是这样的:
先按照一定的规则从数列中抽一些数字,对这些数字进行一个排序,然后把这些数字放回原来的位置,比如对数列【3,8,1,2,5,9,4,7,6,0】进行从小到大排序,我们先抽3,2,4,0这几个数,然后使用插入排序算法对其进行排序。
这四个数的排序结果是0,2,3,4,所以,我们再把排好序的这四个数字,还放回到原来的位置,其他数字的位置或索引都保持不变。数列看起来有序一些了,变成了:
接着我们再抽取0,1,5,3,6这几个数字,继续使用插入排序算法对其进行排序。
这五个数的排序结果是0,1,3,5,6,数列更加有序,变成了:
对于上面这样的数列,就可以认为是大致有序了,最后直接对整个数列使用直接插入排序,这里充分利用了插入排序的优势,整个数列的排序就很顺畅了。
用计算机语言来梳理上面的流程:
先确定一个步长(假设为S),然后从数列中抽第1个,第1+S个,第1+2S个,第1+3S个......数值,用插入排序算法对这些子数列进行排序。
然后我们缩短步长为M(比如令M=S/2),然后从数列中抽第1个,第1+M个,第1+2M个,第1+3M个......数值,继续用插入排序算法对这些子数列进行排序。
接着不断缩短步长,对应每个步长,都执行相同的操作,直至步长为1,此时就相当于对整个数列使用插入排序,希尔排序退化成了普通的插入排序,最后将整个数列排序完毕,但在此之前,数列已经经过了好几次的预排序,数列已经大致有序,最后再使用插入排序,就高效了很多!(这里的步长,也可叫做增量,每轮排序,步长都在缩短,这就是为什么希尔排序又叫缩小增量排序的原因)
java代码实现:
public static int[] shellsort(int[] arr) { int len = arr.length; // 确定步长 int increment = len / 2; while (increment > 0) { // 以步长increment,抽取数字 for (int i = increment; i < len; i = i + increment) { // 对抽取的数字进行插入排序 if (arr[i - increment] > arr[i]) { int temp = arr[i]; int j = i - increment; while (j >= 0 && arr[j] > temp) { arr[j + increment] = arr[j]; j = j - increment; } arr[j + increment] = temp; } } //本轮比较结束后,缩短步长 increment = increment / 2; } return arr; }