希尔排序
介绍:
希尔(shell)排序是一种插入排序,同时也叫缩小增量排序,算是直接插入排序的一个优化算法,以其设计者希尔(Donald Shell)的名字命名,该算法由 1959 年公布。
思想:
将待排序列以一定的步长分成子序列,把子序列进行排序,然后会继续以更小的步长进行分子序列,并将子序列排序,最终算法以步长为 1 进行排序。当步长为 1 时,算法变为直接插入排序,这就保证了数据一定会被全部排序。因此步长的选定很重要。
如待排序列为9个元素,每个元素的校标为0-8,则我们选择初始步长为3,分组,按下标分组分为{0,3,6},{1,4,7},{2,5,8}三组,把这三组在内部进行排序,排序完后再以步长为1,进行排序。则最后对整个序列进行一次直接插入排序即可。
代码:
def ShellSort(input_list):
length = len(input_list)
if length == 0:
return []
sorted_list = input_list
#我们这里取第一次步长为序列长度的一半
#n记录缩小增量的次数标志
n = 1
d = length//2
while d > 0:
for i in range(d,length):
#j是在第i个元素前面的第d个元素
j = i - d
#临时记录i此时的元素
temp = sorted_list[i]
#若下标大于等于0且后面的元素temp小于temp前面第d个元素
#便把前面那个大的元素赋值给湖面temp元素
while j >= 0 and temp < sorted_list[j]:
sorted_list[j+d] = sorted_list[j]
j -=d
#在把temp记录的后面的小的元素赋值给前面那个大元素
#完成:如果后面元素小于前面元素,就调换来那个元素的位置
sorted_list[j+d] = temp
#增量继续减小
d //=2
print("%dth"%n)
print(sorted_list)
#记录增量减小的次数标志
n +=1
return sorted_list
if __name__ == '__main__':
input_list = [50,123,543,187,49,30,0,2,11,100]
print("input_list:")
print(input_list)
sorted_list = ShellSort(input_list)
print("sorted_list:")
print(sorted_list)
结果:
input_list:
[50, 123, 543, 187, 49, 30, 0, 2, 11, 100]
1th
[30, 0, 2, 11, 49, 50, 123, 543, 187, 100]
2th
[2, 0, 30, 11, 49, 50, 123, 100, 187, 543]
3th
[0, 2, 11, 30, 49, 50, 100, 123, 187, 543]
sorted_list:
[0, 2, 11, 30, 49, 50, 100, 123, 187, 543]
分析:
1.算法性能
2.时间复杂度
步长的选择是希尔排序的重要部分,只要最终步长为1任何步长序列都可以工作。算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序,最终算法以步长为1进行排序。当步长为1时,算法变为插入排序,这就保证了数据一定会被排序.步长序列的不同,会导致最坏的时间复杂度情况的不同。因此步长的选择是关键的,一般初始步长用len/2,用这样步长序列的希尔排序比插入排序要快,甚至在小数组中比快速排序和堆排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。
3.算法稳定性
虽然在每次把后面更小的元素换到前面是稳定的,但是在不同的子序列中,还是会打乱相等元素的位置
例如序列{4',3,7,1,65,4},其中为区分两个相等的4,把第一个出现的4记为4',取步长为2,则分为{4',1}、{3,65}、{7,4}三个子序列,
则第一次比较之后为{1,4'}、{3,65}、{4,7},把这三个子序列合成一个子序列为{1,3,4,4',65,7},显然两个4的序列位置反过来了。故Shell排序是一种不稳定的排序算法。
4.和直接插入排序的比较
直接插入排序是稳定的;而希尔排序是不稳定的。
直接插入排序更适合于原始记录基本有序的集合。
希尔排序的比较次数和移动次数都要比直接插入排序少,当N越大时,效果越明显。
直接插入排序也适用于链式存储结构;希尔排序不适用于链式结构。因为断链之后要在重新恢复链式结构就会很大程度上增加时间复杂度,增加的时间复杂度可能超过希尔排序家相比直接插入排序所较少的复杂度,反而适得其反。