Loading

算法基础之希尔排序

希尔排序

希尔排序是直接插入排序的改进版本。

因为直接插入排序对那些几乎已经排好序的数列来说,排序效率极高,达到了 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=希尔排序
posted @ 2021-07-19 10:47  励码万言  阅读(207)  评论(0编辑  收藏  举报