排序算法之希尔排序
前面的文章中提到,冒泡排序因为其无用的交换次数比较多,所以改进了一下,采用记忆化方法,即选择排序。那么在插入排序中,可以看出来,
如果一个列表是逆序的,其比较次数比较多,相应的赋值次数也比较多(O(n^2)),比如[5,4,3,2,1],第一次插入排序时,5移动到4的位置,第二次插入排序时,5又从4移动到3的位置(同时4从5移动到4的位置),以此类推,直到5移动到1的位置,4移动到2的位置。其实一方面插入排序每次只能一步一步的移动一个位置,另一方面,某些移动其实也是没有必要的,即能不能直接移动到比较远的位置,减少无用的移动呢?
如果本来就是排好序的,那么比较次数就是O(n),次数比较少。所以,一个列表越有序,排序时比较次数就越少。那么为了使得插入排序更优,如何提前进行一些操作,使得原始序列能够稍微有序呢?这就是希尔排序。
还是以[5,4,3,2,1]为例子,因为是要移动到较远的位置,即增大移动间距,那么可以从中抽出几个子序列(间隔为2,间隔自己决定),[5,3,1]和[4,2],当然下标不变,还是原来的。那么对这两个子序列分别进行插入排序,可以看出来,5只需要两次移动就到了最终的位置,---->[3,5,1]---->[3,1,5]---->[1,3,5],同理4也是。
那么之后形成的列表是[1,2,3,4,5]。然后再抽出几个子序列(间隔比上一次少),进一步对子序列进行插入排序(本例比较特殊,只需抽取一次)。最后(间隔为1时,此时为最后一次的插入排序)再将整个序列进行一次插入排序,那么这样最坏的情况下,其实是大大减少了移动次数。那么最好的情况下呢?比如[1,2,3,4,5],其实也是一样的,因为本来就有序,所以子序列也是有序的,根本就不需要移动,只需要比较就完事了,不过比较次数增加了一点点,但数量级不变。
那么既不是最坏,也不是最优的情况下呢?[3,2,4,5,1],其实不用考虑了,因为最坏的都已经优化了,那么中等情况下其实也是优化了的。
总结一下,希尔排序其实是根据插入排序的优缺点进行设计的。插入排序的优点是:插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。缺点是:插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(即此时的数据时相当有序的了,此时插入排序较快)。
希尔排序利用了插入排序的简单,同时又避免了插入排序每次交换只能消除一个逆序对的缺点。所以希尔排序一般不是交换相邻元素,而是跳跃一段距离交换。(而这个跳跃了的交换在正常的插入排序中也是不可少的)。
所以,总结来说,希尔排序的交换工作其实是减少了的,但是比较工作是增加了。 简单的说,希尔排序就是多次的插入排序。每次插入排序的输入子序列不一样。
对于希尔排序相对插入排序的优化,目前理解的不是很透彻,时间复杂度取决于子序列的间隔大小,比如,每次长度减半,或者每次长度取1/3等等。如果取2^(k-1),即1,3,5,7,9,…的形式。
那么时间复杂度是O(n^(3/2))。
这是我自己写的,我只选取了2^k的形式。
# 希尔排序2
def shell_sort(alist):
gap = len(alist) // 2
while gap > 0:
for i in range(0, len(alist) - gap, gap):
temp = alist[i+gap]
for j in range(i, -1, -gap):
if alist[j] > temp:
alist[j+gap] = alist[j]
else:
alist[j+gap] = temp
break
else:
alist[j] = temp
# print(alist)
gap = gap // 2
alist = [2,7,5,84,10,1,3,9,8,6]
# alist = [2,1,1,1,1,1,1,1,1]
shell_sort(alist)
print(alist)
这是教程给的代码,给出了两个函数。
# 希尔排序
def shell_sort(alist):
sublistcount = len(alist) // 2
# 以不同的间隔循环进行插入排序
while sublistcount > 0:
# 对每种间隔都的所有子序列都进行插入排序
for startposition in range(sublistcount):
gapInsertionSort(alist, startposition, sublistcount)
print('After increments of size', sublistcount, 'The list is', alist)
sublistcount = sublistcount // 2
def gapInsertionSort(alist, start, gap):
# 默认子序列的第一个元素是有序,所以起始位置是从子序列的第二个元素开始
# 循环,对一种间隔的所有子序列进行插入排序
for i in range(start+gap, len(alist), gap):
currentvalue = alist[i]
position = i
while position >= gap and alist[position - gap] > currentvalue:
alist[position] = alist[position - gap]
position = position - gap
alist[position] = currentvalue
alist = [2,7,5,84,10,1,3,9,8,6]
# alist = [2,1,1,1,1,1,1,1,1]
shell_sort(alist)
print(alist)