一、其他排序
1、希尔排序
一种分组插入排序算法
首先取一个整数 d1=n/2 ,将元素分为 d1 个组,每组相邻两元素之间距离为 d1 ,在各组内进行直接插入排序,相当于分组插入排序
取第二个整数 d2 = d1/2,重复上述分组排序,直到 d1=1,即所有元素在同一组内进行直接插入排序
希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序;最后一趟排序使所有数据有序。
def insert_sort_gap(li,gap):
for i in range(gap, len(li)):
tmp = li[i]
j = i - gap
while li[j] > tmp and j >= 0:
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
时间复杂度:比较复杂,有很多种实现方式,与选取的gap序列有关
最坏情况为 O(n^2),平均情况小于 O(n^2),见维基百科
实际运行情况:比插入排序快,比堆排序慢
2、计数排序
对列表进行排序,已知列表中数的范围都在0到100之间。设计时间复杂度为O(n)的算法
在另一个列表的对应下标(下标值=val)里记录原列表中每一个数val有几个相同的
def count_sort(li,max_count=100):
count = [0 for _ in range(max_count+1)] #建一个列表 都是0
for val in li:
count[val] += 1
li.clear() #li清空 用新列表覆盖
for ind,val in enumerate(count):
for i in range(val):
li.append(ind) #把下标添加val次
import random
li = [random.randint(0,100) for _ in range(20)]
print(li)
count_sort(li)
print(li)
时间复杂度 O(n)
3、桶排序
在计数排序中,如果元素的范围比较大(比如在1到1亿之间),如何改造算法?
桶排序:首先将元素分在不同的桶中(一个范围的数),再对每个桶中的元素排序
def bucket_sort(li,n=100,max_num=10000):
buckets = [[] for _ in range(n)] #创建桶
for val in li:
i = min(val // (max_num // n), n-1) #i 表示val放到几号桶里,如果数为10000,放到99号桶
buckets[i].append(val)
#保持桶内的顺序
for j in range(len(buckets[i])-1,0,-1): #后不包
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,10000) for _ in range(100)]
print(li)
li = bucket_sort(li)
print(li)
桶排序的表现取决于数据的分布,也就是需要对不同的数据排序时采取不同的分桶策略
平均情况时间复杂度:O(n+k) m是桶的个数 k=logm*logn 大约表示一个桶有多少个数
最坏情况时间复杂度:O(n^2 k)
空间复杂度:O(nk)
4、基数排序
多关键字排序,如一个员工表,要求按照薪资排序,薪资相同的按照年龄排序
先按年龄排序再按薪资进行稳定的排序
先按个位数分桶排序,再按十位数分桶排序
def radix_sort(li):
max_num = max(li) #最大值 99 -> 2次, 888 -> 3次
it = 0 #循环次数
while 10 ** it <= max_num: #判断当前是几位数
buckets = [[] for _ in range(10)]
for var in li:
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(100))
random.shuffle(li)
print(li)
radix_sort(li)
print(li)
时间复杂度:O(kn) k:最大值的位数 k=log(10)n
空间复杂度:O(k+n)
O(n):线性时间复杂度
具体效率与数字的分布有关
例题
1、给定两个字符串s和t,判断t是否为s的重新排列后组成的单词
s = "qwertyu" t = "uytrewq" return true
s = "cat" t = "car" return false
方法一:
def isAnagram(s,t):
ss = list(s)
tt = list(t)
ss.sort()
tt.sort()
if ss == tt:
return True
else:
return False
方法二:
def isAnagram2(s,t):
return (sorted(list(s))==sorted(list(t)))
方法三:
def isAnagram3(s,t):
dict1 = {}
dict2 = {}
for ch in s:
dict1[ch] = dict1.get(ch,0) + 1
for ch in t:
dict2[ch] = dict2.get(ch, 0) + 1
return dict1 == dict2
2、给定一个m * n的二维列表,查找一个数是否存在。列表有以下特性:
每一行的列表从左到右已经排序好;每一行第一个数比上一行最后一个数大
方法一:
def searchMatrix(matrix,target):
for line in matrix:
if target in line:
return True
return False
方法二:二分查找法 速度更快
def searchMatrix(matrix,target):
h = len(matrix)
if h == 0:
return False
w = len(matrix[0])
if w == 0:
return False
left = 0
right = h * w - 1
while left < right:
mid = (left + right) // 2
i = mid // w
j = mid % w
if matrix[i][j] == target:
return True
elif matrix[i][j] > target:
right = mid - 1
else:
left = mid + 1
else:
return False
3、给定一个列表和一个整数,设计算法找到两个数的下标,使得两个数之后为给定的整数。保证肯定仅有一个结果
如列表[1,2,3,4] 和目标整数3,1+2=3,结果为(0,1)
def binary_search(self, li, left, right, val):
while left < right:
mid = (left + right) // 2
if li[mid][0] == val:
return mid
elif li[mid][0] > val:
right = mid - 1
else:
left = mid + 1
else:
return None
def twoSum(nums,target):
new_nums = [[num,i] for i,num in enumerate(nums)] #保存nums的下标和数
new_nums.sort(key=lambda x:x[0]) #按照值排好序
for i in range(len(new_nums)):
a = new_nums[i][0]
b = target -a
if b >= a:
j = binary_search(nums, i + 1, len(new_nums)-1, b)
else:
j = binary_search(nums, 0, i - 1, b)
if j:
break
return sorted([new_nums[i][1],new_nums[j][1]])