希尔排序算法(思路分析)
希尔排序也称之为: 缩小增量排序
希尔排序提出的背景:
因为简单插入排序中存在一些问题( 这里我们以升序排序为例 ): 当我们要待插入的数值比较小时后移的次数明显增多,对效率有比较大的影响,所以我们就提出了希尔排序
希尔排序算法介绍:
希尔排序是希尔(Donald shell)于1959年提出的一种排序算法,希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更加高效的版本 , 也称之为 : 缩小增量排序
希尔排序的基本思想:
希尔排序是把记录按下标的一定增量分组,对每个组使用直接插入排序算法进行排序,随着增量主键减小,每组中包含的关键词越来越多,当增量减少到1的时候,整个就会被分为一组,然后就对这一组进行一次直接插入排序,然后算法变终止了
希尔排序算法思路图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z6IAVrS7-1661005216498)(E:\非凡英才\数据结构(java)]\数据结构图解\插入排序过程图解.png)
希尔排序分为两种:
- 交换式
- 位移式
这个就是因为我们的直接插入排序也是有这两种方式,但是我们一般都是使用位移式,不会选择使用交换式,因为交换式和冒泡排序有些类似,执行效率太低,所以我们一般都是使用的位移式,所以这里我们也就是对位移式的希尔排序进行一个讲解
希尔排序的思路分析:
我们首先要知道希尔排序算法的实现是要通过三层for循环来实现的,因为我们的希尔排序就是在每次分组的基础上进行一个直接插入排序,而直接插入排序我们要使用两层循环(外层for循环 + 内层while循环)来解决,而对于我们的分组,也就是自增变量的缩小我们也要通过一层for循环来实现
- 我们首先要定义一个整形变量表示增量 (一般就是定义一个 int gap ), 并来逐步的进行缩小增量,最开始的时候我们的增量是length/2,增量也就等于我们的分组数,也就是最开始的时候就是有length-2组,这个时候注意 :
- 我们这里说的组是要进行排序的元素分为一组,也就是要进行直接插入排序的元素分为一组,那么每个元素和那些元素是一组? 这个元素的索引位置加上后者减去增量(gap)之后对应位置的元素就是和自己一组的元素
- 然后就对每一组进行一个直接插入排序,等把length /2组的都进行直接插入排序之后,我们就将增量设置为length/4,就是有length/4组,然后再重复上述的操作,知道最后分为了一组之后再执行一次插入排序那么算法就算是结束了
- 那么我们如何实现组内的直接插入排序?
- 我们还是定义一个临时变量记录待插入元素,再定义一个临时变量记录待插入的位置的索引
- 注意: 这个时候待插入元素不是从数组中第二个元素开始,而是从第一组的第二个元素开始,那么第一组的第二个元素在什么位置?
- 我们前面已经说过了我们的每一个元素加上增量就是我们的组内的下一个元素,这个时候我们的第一组的第一个元素肯定是索引为0的元素,那么对应的我们就可以计算出我们的第一组中第二个元素就是索引为gap的元素
- 那么具体的我们要如何实现?我们的待插入元素如何变化, 我们的待插入位置如何变化,我们的数值如何后移?
- 我们每次待排序元素变换的时候都是后移一位即可,一个拍完了之后就继续拍下一个就可以了,因为每个组的元素我们都要进行排序, 而对于每次带插入位置前移的时候或者是待插入位置元素后移的时候都是移动gap位, 因为我们这个时候直接插入排序我们不是和前面一位的元素进行比较,而是要和前gap位的元素进行比较,就是要变化gap位, —> 因为我们的每个元素加上或者减去gap之后所得到的位置就是我们的同一个组内的元素位置了
- 这个时候我们还有一个小算法: 移动次数 = 间隔数 + 1
- 我们每次待排序元素变换的时候都是后移一位即可,一个拍完了之后就继续拍下一个就可以了,因为每个组的元素我们都要进行排序, 而对于每次带插入位置前移的时候或者是待插入位置元素后移的时候都是移动gap位, 因为我们这个时候直接插入排序我们不是和前面一位的元素进行比较,而是要和前gap位的元素进行比较,就是要变化gap位, —> 因为我们的每个元素加上或者减去gap之后所得到的位置就是我们的同一个组内的元素位置了
- 那么具体的我们要如何实现?我们的待插入元素如何变化, 我们的待插入位置如何变化,我们的数值如何后移?
- 我们前面已经说过了我们的每一个元素加上增量就是我们的组内的下一个元素,这个时候我们的第一组的第一个元素肯定是索引为0的元素,那么对应的我们就可以计算出我们的第一组中第二个元素就是索引为gap的元素
- 注意: 这个时候待插入元素不是从数组中第二个元素开始,而是从第一组的第二个元素开始,那么第一组的第二个元素在什么位置?
- 我们还是定义一个临时变量记录待插入元素,再定义一个临时变量记录待插入的位置的索引
- 那么我们如何实现组内的直接插入排序?
总结: 直接插入排序中待插入元素的位置和待插入位置变换的时候都是变换一位,而在希尔排序中我们的待插入元素的位置变换的时候是变换一位,但是在希尔排序中我们的待插入位置前移的时候要前移 gap(增量)位
补充:
-
我们定义的gap变量就是表示我们希尔排序中的增量,增量其实也就等于分的组的个数,我们的增量是在length/2之间的,包括length/2和1,所以这个时候我们判断分组退出的条件就是当我们的gap(增量) = 0,这个时候等于0就会退出,那么我们就要让这个循环的判断条件为gap>0,这个时候如果gap=0的时候就会退出循环了
- 关于增量的变换我们一般都是每次都让增量值除以2
-
希尔排序中要定义的变量和我们的直接插入排序中定义的变量是一样的,因为我们的希尔排序就是在分组的基础上进行了一个直接插入排序
-
也就是我们的希尔排序中也是定义如下两个变量:
-
int insertVal = 0; //这里只是定义,但是赋值的时候insertVal也是应该等于0的,但是我们在我们后续的算法中将insertVal的值赋值为了gap,这个时候沃恩的insertVal就不是表示我们的待插入位置的前一个位置,而是直接就是我们的待插入位置
-
int insertIndex = 0: //这里只是定义,具体赋值的时候我们是将insertIndex的值赋值为: gap
-
算法核心:
问题引出: (为什么要提出希尔排序?)
希尔排序就是在直接插入排序的基础上做了一个升级, 我们之前讲过: 简单直接插入排序算法的缺点就是当我们的待排序序列趋于逆序的时候这个时候我们要做的位移或者交换就会有很多次, 效率就会比较低(这里说的位移就是直接将元素后移, 交换就是将元素的位置交换), 如果是使用的交换式的直接插入排序, 如果是遇到完全逆序的情况, 其时间复杂度会和冒泡排序差不多, 都是非常低的, 所以为了解决这个问题, 为了能一定程度的提高直接插入排序算法的时间复杂度, 我们就在简单直接插入排序的基础上提出了希尔排序
算法核心:
希尔排序就是一个逐渐缩小增量的直接插入排序, 就是先将整个待排序序列分成多个序列, 然后在每个序列中进行一个直接插入排序, 然后最终增量将会一直缩小, 直到最终的时候我们的增量会缩小到1, 这个时候我们的序列就会是整个的一个序列, 那么这个时候我们进行一个简单直接插入排序的时候由于我们的序列的混乱度比较低, 这个时候算法(也就是我们的简单直接插入排序的算法时间复杂度就会很高)
- 所以其实我们的希尔排序就是先使用缩小增量的方式, 从而能够逐渐使用较少的局部的简单直接插入排序来降低我们整个待排序序列的混乱度, 从而减少了元素位移或者是交换的次数, 那么元素的位移和交换少了之后我们的算法的时间复杂度就会降低, 算法的效率想对与直接插入排序就会提高