希尔排序
然后进行一次插入排序。
虽然不会直接排好序,但是每一步都使整体数据接近于有序。
# 希尔排序是在插入排序的基础上添加gap参数的一种排序。其实就是把插入排序里面的1改成gap就可以了。
def insert_sort_gap(li, gap):
for i in range(gap, len(li)): # i表示摸到的牌的下标,可以从1的位置可以摸牌。
tmp = li[i] # 手里的牌存起来
j = i - gap # j指的是手里的牌。
while li[j] > tmp and j >= 0: # 比较j-1和刚摸到的j的大小,就往左挪一个。
li[j + gap] = li[j]
j -= gap
li[j + gap] = tmp
def shell_sort(li):
d = len(li) // 2
while d >= 1:
insert_sort_gap(li, d)
d //= 2
li = list(range(1000))
import random
random.shuffle(li)
shell_sort(li)
print(li)
希尔排序比low b三人组快,比nb三人组稍慢一点。
计数排序
讲解计数排序之前我们先来看一个问题:对列表进行排序,已知列表中的数的范围都在0-500之内,设计一个时间复杂度为O(n)的算法。
这就需要用到计数排序,顾名思义,记录某个元素出现了多少次
从左至右依次遍历列表,当某个元素出现时,将此元素出现次数加1,遍历完列表后根据元素出现次数将元素依次排开。
注:元素值从0开始方便列表索引计算
a = [1, 3, 2, 6, 5, 5, 1, 3, 4, 1]
元素值 出现次数
0 0
1 3
2 1
3 2
4 1
5 2
6 1
排序结果:1 1 1 2 3 3 4 5 5 6
# 元素值1出现3次,排列3个1;元素值2出现1次,排列1次, 以此类推。
代码如下:
def count_sort(li, max_count=100): # 需要知道这个数最大是多少。
count = [0 for _ in range(max_count+1)] #最大的数字100则需要长度为101的列表,用列表推导式建立一个包含0-100的列表。
for val in li:
count[val] += 1 #for循环完了列表的所有信息都在count里面。
li.clear() #直接清除原列表,不在生成新列表,节省内容空间
for ind, val in enumerate(count): #遍历下标和值,for循环里, index索引出现了val次
for i in range(val):
li.append(ind) # 把index添加到li,次数为val
import random
li = [random.randint(0,100) for _ in range(1000)]
print(li)
count_sort(li)
print(li)
计数排序的时间复杂度为O(n)。虽然有两个for循环嵌套,但是两个for循环里面的n都不是全部的数字个数n。append的时间复杂度也是n。所以只看上面那个for循环就可以。
桶排序
桶排序是在计数排序上做了一定的修改。如果元素范围比较大或者含有小数,就会占用较大的内存空间。桶排序(Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶里。每个桶再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序),最后依次把各个桶中的记录列出来记得到有序序列。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是比较排序,他不受到O(n log n)下限的影响。
def bucket_sort(li, n=100, max_num=10000):
buckets = [[] for _ in range(n)] # 创建桶。很多桶是一个列表,一个桶又是一个列表。因此是一个二维列表。
for var in li: # 遍历列表里的所有数。
i = min(var // (max_num // n), n-1)
# i决定列表里的数字放到哪个桶里。0->0, 86->0.9999放到999号桶,100应该放到100号桶但是没有。所以放到最后一个桶里(桶号为n-1)。
buckets[i].append(var) # 把var放到桶里了。
# 这是一种写法,可以放进去之后再进行排序。还有一个写法是放进去的过程中就排好序,随时进行冒泡排序,保持桶内的顺序。
for j in range(len(buckets[i])-1, 0, -1): # 把# j从最后一个元素开始。到倒数第二个元素停,写到0是因为前包后不包。
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:
sorted_li.extend(buc)
return sorted_li
import random
li = [random.randint(0,100) for i in range(1000)]
# print(li)
li = bucket_sort(li)
print(li)
桶排序用得不是很多,基数排序相对用得多一些。k表示一个桶多长。
基数排序
第一关键字:工资。第二关键字:年龄。
薪资一样高,排序保证是稳定的。所以先按照年龄排好序,再按薪资排序,就能够得到一个第一关键字工资,第二关键字年龄的稳定的排序。
对一些整数进行排序,也可以看作多关键字排序。两位数也可以看作两个关键字,先排个位进行作为第二关键字;再排十位作为第一关键字进行排序。
先按照个位分桶。
依次出数。满足了个位数小的一定在个位数大的前面。
再按照十位进行分桶排序。
再把桶里的数字挨个出数。
两位数和五位数进行比较,可以把两位数前面加0变成五位数。
# 基数排序代码
def radix_sort(li):
max_num = max(li) # 最大值位99->2,888->3,10000->5
# 方法1为取以十为底的对数。第二种方法:
it = 0 # 迭代多少次。
while 10 ** it <= max_num:
buckets = [[] for _ in range(10)]
for var in li:
# 987 it = 1 取8出来? 987//10 ->98. 98%10=8就可以取8出来了。 取7出来是987%10 it=2,987//100=9%10=9
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)
时间复杂度为kn,k=log(10,n)
而快速排序,时间复杂度为nlogn,logn=log(2,n)
所以基数排序快一些。
增加所有的数字的范围,基数排序的速度会变慢。因为k变大了。基数排序的效率和数字的分布有关。