希尔排序(Shell Sort)是一种分组插入排序算法。希尔排序也是一种插入排序,它是简单插入排序通过改进以后的一个更高效的版本,也称为缩小增量排序,同时该算法是冲破O(n2)的第一批算法之一。python
首先取一个整数d1=n/2,将元素分为d1个组,每组相邻量元素之间距离为d1,在各组内进行直接插入排序;git
取第二个整数d2=d1/2,重复上述分组排序过程,直到di=1,即全部元素在同一组算法
希尔排序每趟并不使某些元素有序,而是使总体数据愈来愈接近有序;最后一趟排序使得全部数据有序。shell
例若有一个列表有九个元素以下所示:app
取d1=9/2获得d1=4,将整个列表分为4组,每组相邻元素间距离为4:dom
将各个组内进行插入排序:函数
排序完成后,将这些元素返回列表:性能
取d2=d1/2,获得d2=2,将整个列表分为2组,每组相邻元素间距为2:测试
再在各个组内进行插入排序:spa
排序完成后,再将这些元素返回列表:
此时d3=d2/2,获得d3=1,此时直接进行插入排序:
如此整个排序过程就完成了。希尔排序每趟并不使某些元素有序,而是使总体数据愈来愈接近有序。
def insert_sort_gap(li, gap): """ 希尔排序的分组进行插入排序 :param li:列表 :param gap:分组的d,将插入排序全部的1改成gap :return: """ for i in range(gap, len(li)): # i表示摸到牌的下标 tmp = li[i] # 摸到的牌 j = i - gap # j指得是手里牌的下标(比摸到的牌小gap) while li[j] > tmp and j >= 0: # 循环条件 li[j + gap] = li[j] # 若是手里的牌大于摸到的牌,将摸到的牌换为以前手里的牌 j -= gap # 手里的牌移动到摸到牌的位置 li[j + gap] = tmp # 将摸到的牌插入有序区 # print(li) # 打印每一趟排序过程 def shell_sort(li): """希尔排序""" d = len(li) // 2 # 取到第一次循环的d值 while d >= 1: # 每次循环d/2,知道d=1的时候结束循环 insert_sort_gap(li, d) d //= 2 # d整除2并赋值给d li = list(range(100)) import random random.shuffle(li) shell_sort(li) print(li)
在希尔排序的理解时,咱们倾向于对于每个分组,逐组进行处理,但在代码实现中,咱们能够不用这么循序渐进地处理完一组再调转回来处理下一组(这样还得加个for循环去处理分组)好比[5,7,4,6,3,1,2,9,8] ,首次增量设gap=length/2=4,则为4组[5,3,8] [7,1] [4,2] [6,9],实现时不用循环按组处理,咱们能够从第gap个元素开始,逐个跨组处理。
from cal_time import * import copy, random def insert_sort_gap(li, gap):... @cal_time def shell_sort(li): """希尔排序""" d = len(li) // 2 # 取到第一次循环的d值 while d >= 1: # 每次循环d/2,知道d=1的时候结束循环 insert_sort_gap(li, d) d //= 2 # d整除2并赋值给d from insert_sort import insert_sort from heap_sort import * li = list(range(10000)) random.shuffle(li) li1 = copy.deepcopy(li) li2 = copy.deepcopy(li) li3 = copy.deepcopy(li) shell_sort(li1) insert_sort(li2) """ shell_sort running time: 0.05884504318237305 secs. insert_sort running time: 4.995609283447266 secs. """ shell_sort(li1) heap_sort(li3) """ shell_sort running time: 0.05833792686462402 secs. heap_sort running time: 0.04569196701049805 secs. """
因而可知希尔排序的运行效率要远远大于插入排序,与堆排序(牛逼三人组中最慢)相比略慢一点。
希尔排序的时间复杂度比较复杂,且与选择的gap(步长)序列有关。
只要最终步长为1任何步长序列均可以工做。算法最开始以必定的步长进行排序。而后会继续以必定步长进行排序,最终算法以步长为1进行排序。当步长为1时,算法变为普通插入排序,这就保证了数据必定会被排序。
Donald Shell最初建议步长选择为n/2而且对步长取半直到步长达到1。虽然这样取能够比O(n2)类的算法(插入排序)更好,但这样仍然有减小平均时间和最差时间的余地。
更多不一样gap状况时间复杂度:https://en.wikipedia.org/wiki/Shellsort#Gap_sequences
对列表进行排序,已知列表中的数范围都在0到100之间。设计时间复杂度为O(n)的算法。
统计每一个数字出现了几回。
def count_sort(li, max_count=100): """ 计数排序 :param li: :param max_count: :return: """ count = [0 for _ in range(max_count+1)] # 生成一个值全为0长度为100+1的列表 for val in li: # 遍历li列表的值 count[val] += 1 # 值对应count列表下标,遍历到便执行加1 # 这样设计也限制了不能用大于100的数字进行排序,由于超出了index下标的范围 li.clear() # 列表清空 for ind, val in enumerate(count): # 下标、值 for i in range(val): # 遍历值(对应下标的统计次数) li.append(ind) # 将下标值添加到li列表中(统计了几回就添加几回) import random li = [random.randint(0, 100) for _ in range(10)] print(li) count_sort(li) print(li)
count[val] += 1是代码的精髓,不只统计了对应值的出现次数,完成了排序,还限制了不能用大于下标范围的数字进行排序,若是超出下标返回,会提示报错。
from cal_time import * @cal_time def count_sort(li, max_count=100): """ 计数排序 :param li: :param max_count: :return: """ count = [0 for _ in range(max_count+1)] # 生成一个值全为0长度为100+1的列表 for val in li: # 遍历li列表的值 count[val] += 1 # 值对应count列表下标,遍历到便执行加1 li.clear() # 列表清空 for ind, val in enumerate(count): # 下标、值 for i in range(val): # 遍历值(对应下标的统计次数) li.append(ind) # 将下标值添加到li列表中(统计了几回就添加几回) @cal_time def sys_sort(li): li.sort() import random,copy li = [random.randint(0, 100) for _ in range(100000)] li1 = copy.deepcopy(li) li2 = copy.deepcopy(li) count_sort(li1) sys_sort(li2) """ count_sort running time: 0.0277559757232666 secs. sys_sort running time: 0.02849888801574707 secs. """
系统排序是用c语言写的,所以运行效率很高,在这里能够看到计数排序效率比系统排序还要高。
对列表进行排序,已知列表中的数范围都在0到100之间。设计时间复杂度为O(n)的算法。0-100须要消耗一个长度是100的列。若是是一亿就要开一亿长的列。
经常使用于年龄等排序。此时再也不往里面append值,而是append对象。
在计数排序中,若是遇到元素值的范围比较大(好比在1到1亿之间),是不适用的,改造算法就有桶排序。
桶排序:首先将元素分在不一样的桶中,再对每一个桶中的元素排序。
def bucket_sort(li, n=100, max_num=10000): """ 桶排序 :param li: :param n: 桶的个数 :param max_num: 数字最大值 :return: """ buckets = [[] for _ in range(n)] # 建立桶:列表生成式生成二维列表,[[], [],...,[]] for var in li: # 遍历列表全部数放在合适的桶里 # i = var // (max_num // n) # i表示var放到几号桶里86//(10000//100)=0,因此放在0号桶,可是处理不了10000 i = min(var // (max_num // n), n-1) # 为了解决10000这个数,将原i值和n-1=99做比较取小 buckets[i].append(var) # 将var放入对应的桶内 # 保持桶内的顺序(插入排序) for j in range(len(buckets[i])-1, 0, -1): # 在列表[4,7,2,5]从后往前倒着取值 if buckets[i][j] < buckets[i][j-1]: # 若是后面的元素小于前一个元素就交换它 buckets[i][j], buckets[i][j-1] = buckets[i][j-1], buckets[i][j] else: break # 将桶里的数输出出来 sorted_li = [] for buc in buckets: # buc是每个桶 sorted_li.extend(buc) return sorted_li import random li = [random.randint(0,10000) for i in range(100000)] print(li) li = bucket_sort(li) print(li)
桶排序的表现取决于数据的分布,也就是须要对不一样的数据排序时采起不一样的分桶策略。
n是列表的长度,k是桶的个数。
平均状况事件复杂度:O(n+k),相似于线性的复杂度。
最坏状况时间复杂度:O(n2k),最坏状况下比O(n2)还要高。
空间复杂度:O(nk),占用了一个桶的空间,占用了O(nk)。
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的做用,基数排序法是属于稳定性的排序。
假如如今有一个员工表,要求按照薪资排序,薪资相同的员工按照年龄排序。
先按照年龄进行排序,再按照薪资进行稳定的排序。
对32,13,94,52,17,54,93排序,是否能够看做多关键字排序?能够,十位看做是第一关键字,个位看做是第二关键字。
先按照个位分桶:
接下来依次输出每一个桶的数据:
这样就实现了按个位数进行排序,知足了个位数小的必定在前面
接下来要按照十位数来分桶:
再次依次输出每一个桶的数据:
def radix_sort(li): """基数排序,装桶输出不作排序""" max_num = max(li) # 最大值99->2, 888->3, 10000->5 it = 0 while 10 ** it <= max_num: # 若是10的it次方小于等于max_num,即知足循环条件 buckets = [[] for _ in range(10)] # 生成10个桶 for var in li: """ 取数字个位数的值:987%10 取余 取数字十位数的值:(987//10)%10 取整再取余 取数字百位数的值:(987//100)%10 对100取整再取余 """ digit = (var // 10 ** it) % 10 buckets[digit].append(var) # 分桶 # 分桶完成,将数据依次取出 li.clear() for buc in buckets: li.extend(buc) # 将数据从新写回li it += 1 import random li = list(range(1000)) random.shuffle(li) radix_sort(li) print(li)
分拆函数的写法:
def list_to_buckets(li, base, iteration): """ 列表到桶 :param li: :param base: 分的桶的个数 :param iteration: 装桶是第几回迭代 :return: """ buckets = [[] for _ in range(base)] for number in li: digit = (number // (base ** iteration)) % base buckets[digit].append(number) return buckets def buckets_to_list(buckets): return [x for bucket in buckets for x in bucket] # li = [] # for bucket in buckets: # for num in bucket: # li.append(num) def radix_sort(li, base=10): maxval = max(li) it = 0 while base ** it <= maxval: li = buckets_to_list(list_to_buckets(li, base, it)) it += 1 return li import random li = [random.randint(0,100) for _ in range(10)] random.shuffle(li) s = radix_sort(li) print(s) # [3, 10, 22, 27, 38, 43, 45, 54, 59, 72]
时间复杂度:O(kn),这里的k = log10n,而快速排序是O(nlog2n)。因而可知基数排序比快排要快。可是当数字范围愈来愈大,k愈来愈大时,效率会慢于快排。
空间复杂度:O(k+n),基数排序空间上会消耗一个桶,空间消耗也是比较大的。所以最经常使用的仍是快速排序和python自带的排序