创作不易,来了的客官点点关注,收藏,订阅一键三连❤😜
前言
程序=数据结构+算法,算法是数学理论和工程实现的杂糅,是一个十分有趣神奇的学问。搞懂算法用另一种视角看编程,又会是一种全新的感受,如果你也在学习算法,不妨跟主任萌新超差一起学习,拿下算法!
系列文章目录
python每日算法 | 分治法与归并排序,你还在担心被面试官问归并算法吗?
python每日算法 | 图文+“农村包围城市”详解堆排序,手把手学会
python每日算法 | 图文结合详解快速排序,手撕快排代码!
概述
本期的内容将介绍十大排序算法之希尔排序,通过本期内容你将掌握希尔排序如何用python实现以及希尔排序与插入排序、堆排序效率的比较!
目录
超超python每日算法思维导图
希尔排序
什么是希尔排序?
希尔排序(Shell Sort),也称递减增量排序算法,是⼀种分组插⼊排序算法。是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
更详细思路如下:
⾸先取⼀个整数d1=n/2,将元素分为d1个组,每组相邻量元素之间距离为d1,在各组内进⾏直接插⼊排序;
取第⼆个整数d2=d1/2,重复上述分组排序过程,直到 di=1,即所有元素在同⼀组内进⾏直接插⼊排序。
shellsort with gaps 23,10,4,1
实例理解希尔排序
例如我们有以下列表:
5 | 7 | 4 | 6 | 3 | 1 | 2 | 9 | 8 |
我们可以得到d=4(9//2),那么接下来将列表分为四组,位置相隔为4(d)的为一组,那么可以分为4组即:
5 | 3 | 8 |
7 | 1 |
4 | 2 |
6 | 9 |
接下来分别做插入排序,得到如下:
3 | 5 | 8 |
1 | 7 |
2 | 4 |
6 | 9 |
随后回到原列表,得到如下列表:
3 | 1 | 2 | 6 | 5 | 7 | 4 | 9 | 8 |
接下来d=d/2=2,在分组分为了两组,并插入排序得到:
2 | 3 | 4 | 5 | 8 |
1 | 6 | 7 | 9 |
随后合并:
2 | 1 | 3 | 6 | 4 | 7 | 5 | 9 | 8 |
随后d=d/2=1,直接插入排序:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
以上便是希尔排序的过程。
思考:
我们会觉得希尔排序排这么多次,还调用这么多次插入排序,不是挺复杂吗?
希尔排序每趟并不使某些元素有序,⽽是使整体数据越来越接近有序;最后⼀趟排序使得所有数据有序。
希尔排序代码的实现
# 因为希尔排序是利用了插入排序的思想,因此我们可以在插入排序算法的基础上改
def insert_sort_gap(lst,gap): # gap即为d,即间隔
for i in range(gap,len(lst)): # 从gap开始
tmp = lst[i] #
j = i - gap # j代表的是手里的牌的下标,换为希尔排序,那么就是和gap距离的元素相比较
while lst[j] > tmp and j >= 0:
# 说明lst[i-gap]>lst[i],即需要调整元素的情况,即比如实例中的3(lst[i])和5(lst[i-gap]),5>3且5的位置》0进行调整
lst[j+gap] = lst[j] # 那么调整就是把lst[i-gap]赋值给新的lst[i]即lst[j+gap],这样保证了让小的排到前面,最终输出升序
j -= gap
lst[j+gap] = tmp # lst[j+1]是用来存放要插入的牌
def shell_sort(lst):
d = len(lst)//2 # 求d
while d >= 1: # 最终d=1进行最后一次循环,因此d》=1进行循环
insert_sort_gap(lst,d) # 进行插入排序
d //= 2 # 产生下一个d
print(lst)
# 检测希尔排序
lst1 = [i for i in range(14)]
import random
random.shuffle(lst1)
print(f"{lst1}")
shell_sort(lst1)
# 输出结果
# [0, 2, 10, 7, 4, 9, 11, 6, 8, 12, 13, 1, 5, 3]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
希尔排序与插入排序、堆排序的效率比较
from runtime import *
def insert_sort_gap(lst,gap): # gap即为d,即间隔
for i in range(gap,len(lst)): # 从gap开始
tmp = lst[i] #
j = i - gap # j代表的是手里的牌的下标,换为希尔排序,那么就是和gap距离的元素相比较
while lst[j] > tmp and j >= 0:
# 说明lst[i-gap]>lst[i],即需要调整元素的情况,即比如实例中的3(lst[i])和5(lst[i-gap]),5>3且5的位置》0进行调整
lst[j+gap] = lst[j] # 那么调整就是把lst[i-gap]赋值给新的lst[i]即lst[j+gap],这样保证了让小的排到前面,最终输出升序
j -= gap
lst[j+gap] = tmp # lst[j+1]是用来存放要插入的牌
@runtime # 调用装饰器计算希尔排序时间
def shell_sort(lst):
d = len(lst)//2 # 求d
while d >= 1: # 最终d=1进行最后一次循环,因此d》=1进行循环
insert_sort_gap(lst,d) # 进行插入排序
d //= 2 # 产生下一个d
# 插入排序
@runtime # 调用装饰器计算插入排序时间
def insert_sort(lst):
for i in range(1,len(lst)): # i表示摸到的牌的下标
tmp = lst[i] # tmp代表摸到的牌
j = i - 1 # j代表的是手里的牌的下标,手上自动已有第一张牌
while lst[j] > tmp and j >= 0: # 需要移动有序区牌的情况
lst[j+1] = lst[j]
j -= 1
lst[j+1] = tmp # lst[j+1]是用来存放要插入的牌
# 堆排序
def shift(lst,low,high): # low:对根节点的位置;high:堆最后一个元素的位置
i = low # 标记low
j = 2 * i + 1 # j代表左孩子位置
tmp = lst[low] # 把堆顶存起来
while j <= high: # 只要j位置有元素,就循环
if j + 1 <= high and lst[j+1] > lst[j]: # 首先判断是否j这一层有右孩子(j + 1直的j这一层的另一个数),其次判断j这一层元素的大小,j+1(有孩子)大于j,则j指向j+1
j = j + 1 # j指向有孩子
if lst[j] > tmp: # 然后判断j和堆顶的元素(tmp)的大小,如果j位置的元素大于堆顶元素,则堆顶元素和j(左孩子)位置互换
lst[i] = lst[j]
i = j # 继续看下一层
j = 2 * i + 1
else: # tmp最大,则把tmp放到i的位置上
lst[i] = tmp # 把tmp放到某一级
break
else:
lst[i] = tmp # 把tmp放到叶子节点上
# 堆排序主函数
@runtime # 装饰器计算堆排序时间
def heap_sort(lst):
n = len(lst) # 获取列表长度
for i in range((n-2)//2,-1,-1):
# i代表建堆时调整部分的根的下标,(n-2)//2是得到位置,n-1是孩子节点下标,(n-1-1)//2代表根节点的下标
shift(lst,i,n-1) # i为堆顶,high为最后一个节点
# 建堆完成
for i in range(n-1,-1,-1): # i指向最后一个节点
lst[0],lst[i] = lst[i],lst[0] # 堆顶元素lst[0]和最后一个节点位置互换
shift(lst,0,i - 1) # i - 1代表新的high
# return lst
# 检验效率
lst1 = [i for i in range(10000)]
import random,copy
random.shuffle(lst1)
lst1_1 = copy.deepcopy(lst1)
lst1_2 = copy.deepcopy(lst1)
lst1_3 = copy.deepcopy(lst1)
shell_sort(lst1_1)
insert_sort(lst1_2)
heap_sort(lst1_3)
# 输出结果
# shell_sort执行用时0.058002471923828125s
# insert_sort执行用时8.6656973361969s
# heap_sort执行用时0.08600521087646484s
希尔排序的时间复杂度
希尔排序的时间复杂度讨论⽐较复杂,并且和选取的gap序列有关,有很多大佬在研究希尔排序不同的gap取值的时间复杂度的计算。
超超能力有限,因此,大家有兴趣可以去探究!
创作不易,客官点个赞,评论一下吧!超超和你一起加油❤😜