1959 年一个叫Donald L. Shell (March 1, 1924 – November 2, 2015)
的美国人在Communications of the ACM 国际计算机学会月刊
发布了一个排序算法,今后名为希尔排序的算法诞生了。算法
注:ACM = Association for Computing Machinery
,国际计算机学会,世界性的计算机从业员专业组织,创立于1947年,是世界上第一个科学性及教育性计算机学会。segmentfault
希尔排序是直接插入排序的改进版本。由于直接插入排序对那些几乎已经排好序的数列来讲,排序效率极高,达到了O(n)
的线性复杂度,可是每次只能将数据移动一位。希尔排序创造性的能够将数据移动n
位,而后将n
一直缩小,缩到与直接插入排序同样为1
,请看下列分析。数组
希尔排序属于插入类排序算法。数据结构
有一个N
个数的数列:并发
N
的整数d1
,将位置是d1
整数倍的数们分红一组,对这些数进行直接插入排序。d1
的整数d2
,将位置是d2
整数倍的数们分红一组,对这些数进行直接插入排序。d2
的整数d3
,将位置是d3
整数倍的数们分红一组,对这些数进行直接插入排序。d=1
,接着使用直接插入排序。这是一种分组插入方法,最后一次迭代就至关因而直接插入排序,其余迭代至关于每次移动n
个距离的直接插入排序,这些整数是两个数之间的距离,咱们称它们为增量。数据结构和算法
咱们取数列长度的一半为增量,之后每次减半,直到增量为1。函数
举个简单例子,希尔排序一个 12 个元素的数列:[5 9 1 6 8 14 6 49 25 4 6 3]
,增量d
的取值依次为:6,3,1
:code
x 表示不须要排序的数 取 d = 6 对 [5 x x x x x 6 x x x x x] 进行直接插入排序,没有变化。 取 d = 3 对 [5 x x 6 x x 6 x x 4 x x] 进行直接插入排序,排完序后:[4 x x 5 x x 6 x x 6 x x]。 取 d = 1 对 [4 9 1 5 8 14 6 49 25 6 6 3] 进行直接插入排序,由于 d=1 彻底就是直接插入排序了。
越有序的数列,直接插入排序的效率越高,希尔排序经过分组使用直接插入排序,由于步长比1
大,在一开始能够很快将无序的数列变得不那么无序,比较和交换的次数也减小,直到最后使用步长为1
的直接插入排序,数列已是相对有序了,因此时间复杂度会稍好一点。协程
在最好状况下,也就是数列是有序时,希尔排序须要进行logn
次增量的直接插入排序,由于每次直接插入排序最佳时间复杂度都为:O(n)
,所以希尔排序的最佳时间复杂度为:O(nlogn)
。排序
在最坏状况下,每一次迭代都是最坏的,假设增量序列为:d8 d7 d6 ... d3 d2 1
,那么每一轮直接插入排序的元素数量为:n/d8 n/d7 n/d6 .... n/d3 n/d2 n
,那么时间复杂度按照直接插入的最坏复杂度来计算为:
假设增量序列为 ⌊N/2⌋ ,每次增量取值为比上一次的一半小的最大整数。 O( (n/d8)^2 + (n/d7)^2 + (n/d6)^2 + ... + (n/d2)^2 + n^2) = O(1/d8^2 + 1/d7^2 + 1/d6^2 + ... + 1/d2^2 + 1) * O(n^2) = O(等比为1/2的数列和) * O(n^2) = O(等比求和公式) * O(n^2) = O( (1-(1/2)^n)/(1-1/2) ) * O(n^2) = O( (1-(1/2)^n)*2 ) * O(n^2) = O( 2-2*(1/2)^n ) * O(n^2) = O( < 2 ) * O(n^2)
因此,希尔排序最坏时间复杂度为O(n^2)
。
不一样的分组增量序列,有不一样的时间复杂度,可是没有人可以证实哪一个序列是最好的。Hibbard
增量序列:1,3,7,···,2n−1
是被证实可普遍应用的分组序列,时间复杂度为:Θ(n^1.5)
。
希尔排序的时间复杂度大约在这个范围:O(n^1.3)~O(n^2)
,具体还没法用数学来严格证实它。
希尔排序不是稳定的,由于每一轮分组,都使用了直接插入排序,但分组会跨越n
个位置,致使两个相同的数,发现不了对方而产生了顺序变化。
package main import "fmt" // 增量序列折半的希尔排序 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 main() { list := []int{5} ShellSort(list) fmt.Println(list) list1 := []int{5, 9} ShellSort(list1) fmt.Println(list1) list2 := []int{5, 9, 1, 6, 8, 14, 6, 49, 25, 4, 6, 3} ShellSort(list2) fmt.Println(list2) list3 := []int{5, 9, 1, 6, 8, 14, 6, 49, 25, 4, 6, 3, 2, 4, 23, 467, 85, 23, 567, 335, 677, 33, 56, 2, 5, 33, 6, 8, 3} ShellSort(list3) fmt.Println(list3) }
输出:
[5] [5 9] [1 3 4 5 6 6 6 8 9 14 25 49] [1 2 2 3 3 4 4 5 5 6 6 6 6 8 8 9 14 23 23 25 33 33 49 56 85 335 467 567 677]
按照以前分析的几种排序算法,通常建议待排序数组为小规模状况下使用直接插入排序,在规模中等的状况下可使用希尔排序,但在大规模仍是要使用快速排序,归并排序或堆排序。
我是陈星星,欢迎阅读我亲自写的 数据结构和算法(Golang实现),文章首发于 阅读更友好的GitBook。