其他算法(待完善)
内容概要
一、希尔算法
二、计数算法
三、桶排序
四、基数算法
1、希尔算法
希尔算法是插入算法的一种优化。
希尔算法每遍历一次,并不使得列表某些元素之间有序,只是使得整体的元素趋向于有序
希尔算法的一种实现方式
对于n个元素的列表,将列表分为d1个组,d1等于(n // 2),每组每个元素的间隔也为d1,对这d1组都进行插入排序
再将n个元素的列表,分为d2个组,d2等于(d1 // 2),每组每个元素的间隔为d2,对这d2组都进行插入排序
...
将n个元素的列表分为dn个组,dn等于(dn-1 // 2),每组每个元素的间隔为dn,对这dn组都进行插入排序
直到dn = 1,对整个列表进行最后一次插入排序,列表排序完成
希尔算法代码实现
def insert_sort_gap(li, x=2): n = len(li) gap = n // x while gap >= 1: for i in range(gap, n): tmp = li[i] for j in range(i-gap, -1, -gap): if li[j] > tmp: li[j+gap] = li[j] else: li[j+gap] = tmp break else: li[i % gap] = tmp # 利用余数可以找到每组有序区中一开始元素的下标 gap = gap // 2
希尔算法分组示意图
**待补充**
希尔算法的时间复杂度
希尔算法的时间复杂度可能涉及数学问题,比较复杂,它的时间复杂度跟如何分组有关
**待补充**
2、计数算法
计数算法之前的算法都属于比较算法,比较算法经过数学证明,最快的时间复杂度为O(nlogn)
在知道要排序的列表中数值的范围时(比如所有数都在1~100之间),就可以使用计数算法
计数算法代码实现
def count_sort(li, max_num): tmp_li = [0 for _ in range(max_num+1)] # 创建一个用于存储信息的列表,索引对应原列表的值,值对应原列表中的值出现的次数 for i in li: tmp_li[i] += 1 li.clear() for index, val in enumerate(tmp_li): for _ in range(val): li.append(index) import random # li = [random.randrange(101) for _ in range(100)] li = [i for i in range(101)] random.shuffle(li) print(li) count_sort(li, 100) print(li)
计数算法示意图解
**待补充**
计数算法的时间复杂度
计数算法的时间复杂度很快,为O(n),但是使用计数算法是有限制的
一、是必须知道数据的范围
二、计数算法消耗内存,它的空间复杂度为O(m),m指的是数据最大值与数据最小值的差值,也就是数据跨度
比如存在5个数的列表,知道它的范围为1~10000000,计数排序便要创建一个长度为10000000的列表,之后还要遍历这10000000长的列表
所以只有部分情况下可以使用计数排序
一、已知数据的跨度
二、数据的跨度尽量小
三、数据中存在小数尽量不使用,存在负数是有办法排序的(在索引对应值中修改下就好)
3、桶排序
桶排序可以看作是计数排序的改良
计数排序将列表的索引看作是一个桶,用来装和它索引相同的值
桶排序将一个容器类型看作桶(这里用列表),它不止存放唯一值,而是存放一个范围的值
桶排序代码实现
import random li1 = list(range(100001)) random.shuffle(li1) def bucket_sort(li, max_num, bucket_size): buckets = [[] for _ in range(max_num // bucket_size)] # 创建一个二级列表 for num in li: if num == max_num: buckets[-1].append(num) continue i = num // bucket_size buckets[i].append(num) # 放入桶时,就对桶内元素进行排序(插入排序) for j in range(len(buckets[i])-2, -1, -1): if num < buckets[i][j]: buckets[i][j+1] = buckets[i][j] else: buckets[i][j+1] = num break else: buckets[i][0] = num # 将所有元素放入桶后,再对每个桶内的元素进行排序 # for buc in buckets: # heap_sort(buc, 0, len(buc)-1) # 依次将桶内元素放回原列表 li.clear() for buc in buckets: li.extend(buc) print(li1) bucket_sort(li1, 100000, 100) print("==============================================") print(li1)
桶排序示意图解
**待补充**
桶排序的时间复杂度
桶排序的时间复杂度比较复杂,它的时间复杂度与数据的分布有关
数据分布得越均匀,它的效率越高;
如果数据分布得不均匀
比如对于一个列表中的数据,最大值为10000,跨度设为1000,其中1%的数在0~8999中,99%的数在9000~10000中
按照上面代码的逻辑,1%的数将分配到9个桶中,99%的数分配到一个桶中;那么对于最后一个桶进行插入排序的话,就相当于对99%的数进行插入排序(效率很慢)
正确的分法是将0~8999的数用一个桶存放,将9000-10000的数据再细分多个桶存放
将创建列表的式子改为,桶排序可能发生最坏情况
li1 = [random.randrange(10001) for _ in range(10001)]
**待补充,桶排序的具体时间复杂度**
总之,桶排序不太好用,使用得少
4、基数算法
基数算法是通过对数据得个位进行排序,再对十位进行稳定的排序,再对百位进行稳定的排序...以此类推,最后依次取出,排序完成
基数算法的思想与多关键字查询有关
比如,要根据用户的年龄升序排序,年龄相同的根据薪资排序。
可能与想象的不太一样,这里是先要按照薪资进行一次排序(并不是先按照年龄排序)
再对年龄进行稳定的排序(年龄相同的数据,将会保证保持按薪资排序后的相对位置不变;年龄不同的数据,不受薪资排序的影响,相对位置可能发生改变——后排序的字段优先级更高)
如果先按照年龄排序,再按照薪资排序将会遇到一个问题
按照年龄排序后的列表可能存在多处,年龄相同的地方,要怎么记录这些相同的位置(也能够实现,但没有上面方法简单)。
基数算法代码实现
import random li1 = list(range(100000)) random.shuffle(li1) # print(li1) def radix_sort(li): buckets = [[] for _ in range(10)] max_num = max(li) n = 1 while 10**(n-1) <= max_num: # 基数循环的次数是依照最大值的位数决定的,比如1024,就执行4次循环;15474就执行5次循环 for num in li: i = num // 10**(n-1) % 10 # i获取到个位、十位、或者百位上的数 buckets[i].append(num) li.clear() for buc in buckets: # 将桶里面的数据放到列表中 li.extend(buc) for buc in buckets: # 清空桶 buc.clear() n += 1 radix_sort(li1) print(li1)
基数算法示意图解
**待补充**
基数算法的时间复杂度
综上,基数算法的时间复杂度为O(kn),n指的是列表的长度,k根据最大值而来,k等于log max_num(以10为底的,以max_num为真数的对数)
当列表的最大值较小时,基数排序比快速排序更快
import random import time li = list(range(100000)) random.shuffle(li) # print(li) li1 = li.copy() li2 = li.copy() def tell_time(func): def wrapper(*args, **kwargs): s_time = time.time() res = func(*args, **kwargs) e_time = time.time() print(e_time - s_time) return res return wrapper @tell_time def radix_sort(li): buckets = [[] for _ in range(10)] max_num = max(li) n = 1 while 10**(n-1) <= max_num: for num in li: i = num // 10**(n-1) % 10 buckets[i].append(num) li.clear() for buc in buckets: li.extend(buc) for buc in buckets: buc.clear() n += 1 def position(li, head, end): tmp = li[head] i = head j = end while i < j: while i < j and li[j] >= tmp: j -= 1 li[i] = li[j] while i < j and li[i] <= tmp: i += 1 li[j] = li[i] else: li[i] = tmp return i def _quick_sort(li, head, end): if head < end: mid = position(li, head, end) _quick_sort(li, head, mid-1) _quick_sort(li, mid+1, end) @tell_time def quick_sort(li, head, end): _quick_sort(li, head, end) quick_sort(li1, 0, len(li1)-1) radix_sort(li2) 结果: 0.3480687141418457 0.17054438591003418
但是基数算法也有缺点,比如它适合数字跨度较小的数字结合,并且无法比较小数。在比较字符串时要注意,对于不同位数的整数,要在前置位补0;对于字符串,要在后置位补0。
91110 120 #前置位补0 91110 00120 "abcdwewat" "abcd" #后置位补0 "abcdwewat" "abcd00000"
***待完善***
本文来自博客园,作者:口乞厂几,转载请注明原文链接:https://www.cnblogs.com/laijianwei/p/14623976.html

浙公网安备 33010602011771号