算法基础之希尔排序
希尔排序
希尔排序是直接插入排序的改进版本。
因为直接插入排序对那些几乎已经排好序的数列来说,排序效率极高,达到了 O(n) 的线性复杂度,但是每次只能将数据移动一位。希尔排序创造性的可以将数据移动 n 位,然后将 n 一直缩小,缩到与直接插入排序一样为 1。
希尔排序属于插入类排序算法。
有一个 N 个数的数列:
先取一个小于 N 的整数 d1,将位置是 d1 整数倍的数们分成一组,对这些数进行直接插入排序。
接着取一个小于 d1 的整数 d2,将位置是 d2 整数倍的数们分成一组,对这些数进行直接插入排序。
接着取一个小于 d2 的整数 d3,将位置是 d3 整数倍的数们分成一组,对这些数进行直接插入排序。
直到取到的整数 d=1,接着使用直接插入排序。
这是一种分组插入方法,最后一次迭代就相当于是直接插入排序,其他迭代相当于每次移动 n 个距离的直接插入排序,这些整数是两个数之间的距离,我们称它们为增量。
我们取数列长度的一半为增量,以后每次减半,直到增量为1。
复杂度
希尔排序最坏时间复杂度为 O(n^2)。
不同的分组增量序列,有不同的时间复杂度,但是没有人能够证明哪个序列是最好的。Hibbard 增量序列:1,3,7,···,2n−1 是被证明可广泛应用的分组序列,时间复杂度为:Θ(n^1.5)。
希尔排序的时间复杂度大约在这个范围:O(n^1.3)~O(n^2),具体还无法用数学来严格证明它。
希尔排序不是稳定的,因为每一轮分组,都使用了直接插入排序,但分组会跨越 n 个位置,导致两个相同的数,发现不了对方而产生了顺序变化。
代码
// 增量序列折半的希尔排序
func ShellSort(list []int) {
// 数组长度
n := len(list)
// 每次减半,直到步长为1
for step := n / 2;step >= 1;step /= 2{
// 开始插入排序,每一轮的步长为step
for i :=step;i <n;i+=step{
for j:= i - step;j >= 0;j -= step{
// 满足插入条件交换元素
if list[j+step] < list[j] {
list[j],list[j + step] = list[j + step],list[j]
continue
}
break
}
}
}
}
// 写法二
func ShellSort2(list []int) {
// 数组长度
n := len(list)
// 进行分组,每次对半分组,直到步长为1
for step := n/2;step >= 1;step /= 2 {
// 对每一组进行插入排序
for i := step;i < n;i++ {
deal := list[i]
j := i - step
for ;j >= 0 && deal < list[j];j -= step {
list[j + step] = list[j]
}
list[j + step] = deal
}
}
}
总结
希尔排序还是挺绕的,首先要理解它的原理,先分组,然后通过步长进行组与组之间对应元素的比较,采用插入排序的方式。
结合代码多敲几遍加深理解,方法二理解性更好一点,排序动态图如下:
参考(感谢)
http://www.topgoer.cn/docs/goalgorithm/goalgorithm-1cm6avl1oatp4
https://mp.weixin.qq.com/s/4kJdzLB7qO1sES2FEW0Low
https://interviewguide.cn/#/Doc/Knowledge/算法/算法基础/十大排序?id=希尔排序