关于比较排序算法的下界:
显而易见,对于一个含有n个数字的数组,他们的排列方式有n*(n-1)*(n-2)*(n-3)……=n!种。对于其中仅存的一种正确的排序方式,我们需要做的事情是将剩下的所有合理的状态调整成这一种正确的排序方式。也就是说我们完成的是一个从n!定位到1的过程。
如果我们逆向地思考这一过程:从1如果能达到所有合理的n!个状态,也就说明了采用的方法可以成功地将数列排序。然而比较算法的基本原理决定了我们采用的方式是比较数的大小,而比较数的大小,我们走向两个分支:【大于或者小于等于】、【大于等于或者小于】(一般讨论的时候很少将等于单独拿出来讨论,毕竟相等的情况在排序的时候是一样的),也就是可以看成一棵二叉树,二叉树的层数,表征着我们比较出结果需要用的比较的次数,也就是需要的时间。
对于二叉树的知识告诉我们,显然这棵二叉树是完全二叉树的时候层数是最小的,也就是最快的算法,而我们熟知的那些诸如插入排序一定有一些比较是无效的,也就是说整个二叉树显得比较高比较瘦,而矮矮胖胖的二叉树才是较快的算法,接下来我们针对一棵完全二叉树进行讨论:
对于一棵有l个叶节点的决策树,应该有 n! <= l <= 2^h(k是二叉树的层数),那么显然:
h >= lg(n!) 从而 h = O(nlgn)
这也就意味着,一个比较排序,无论如何优化,他的时间复杂度最多被优化到nlgn级别,这是由它本身的决策类型以及节点数量决定的。
计数排序:
计数排序不是比较排序中的一种,是通过记录集合中出现的次数实现的,简而言之例如现在有n个1-100之间的数(1和100也是排序检测数组最大最小值得到的) ,那就开一个有100个下标的数组,然后遍历整个数组,出现一次就在对应的位置加一,将整个数组内每一个元素出现的个数给记录下来,之后再按照顺序和次数输出就可以。思路非常简单,python源代码如下:
def jishu(arr):
"""
计数排序算法
:param arr: 等待排序的列表
:return: 排序完毕的列表
"""
# 确定数组的最大值和最小值
min = arr[0]
max = arr[0]
for i in arr:
if i > max:
max = i
if i < min:
min = i
jishu_list = []
ans = []
for i in range(min, max+1): # 开辟新的存储空间存出现次数
jishu_list.append(0)
for i in arr: # 统计出现次数
jishu_list[i-min] += 1
for i in range(0, len(jishu_list)): #生成答案数组
for j in range(0, jishu_list[i]):
ans.append(i+min)
return ans
if __name__ == '__main__':
# a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
a = [16, 14, 10, 9, 8, 7, 4, 3, 2, 1, 16, 14, 10, 9, 8, 7, 4, 3, 2, 1]
print(jishu(a))
计数排序存在的问题是,如果上界和下界差很大的时候需要非常巨大但是没怎么利用的存储空间,特别是数据量不是很多的时候就更加浪费存储空间,适合范围相对固定,重复数据很多的样本。
桶排序:
为了连贯性,稍稍变更一下书里面的顺序,先记录一下和计数排序有很多相似之处的桶排序。桶排序是为了节省一部分的存储空间,但是又不至于回到比较排序的nlgn时间的折中方案,基本思想是把最大值和最小值之间的所有空间分成n个平均大小的空间(也就是“桶”),然后遍历整个数组,扔到桶里面的同时,对桶里的所有元素进行排序,最后按照桶的顺序相应输出就可以。
我们针对一个桶进行讨论:显然桶内会有很多数据进出,而且会有插入操作,那么用链表数据结构是比较合适的,python的话我们可以直接使用简单的列表以及它的insert方法实现。
def tong(arr, tong_num):
"""
桶排序算法
:param arr: 等待排序的列表
:param tong_num: 桶子个数
:return: 排序完毕的列表
"""
# 确定数组的最大值和最小值
min = arr[0]
max = arr[0]
size = len(arr) // tong_num # 每个桶的大小
for i in arr:
if i > max:
max = i
if i < min:
min = i
tong_lists = [] # 初始化桶
for i in range(0, size-1):
tong_lists.append([])
for num in arr:
tong_id = (num - min) // size
if not tong_lists[tong_id]: # 如果桶是空的直接扔进去
tong_lists[tong_id].append(num)
else:
for i in range(0, len(tong_lists[tong_id])):
if tong_lists[tong_id][i] >= num: # 新插入的数依次比较,找到第一个大于等于它的就插它前面
tong_lists[tong_id].insert(i, num)
break
print(tong_lists)
ans = [] # 处理成能输出的答案
for tong_list in tong_lists:
for num in tong_list:
ans.append(num)
return ans
if __name__ == '__main__':
# a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
a = [16, 14, 10, 9, 8, 7, 4, 3, 2, 1, 16, 14, 10, 9, 8, 7, 4, 3, 2, 1]
print(tong(a, 4))
基数排序:
基数排序的思想在于,先假设将所有的数码用0补齐成为和最大的数一样长,然后从低位到高位依次排序,由于这个排序过程类似于分类(按照正在排序的位置的数字分类)+排序的过程,因此可以用类似桶排序的方式做10个并列的小列表来存储。然而列表是有序的,可以之前排序过的内部顺序也会随着这个操作保留下来,依次排序到最高位的数字就可以看到整个数组按照顺序排序了。
def jishupaixu(arr):
"""
基数排序算法:
:param arr: 输入的无序数组
:return: 返回的数组
"""
sorting = 1
max_num = max(arr)
max_pos = len(str(max_num))
while sorting <= max_pos:
lists = [[] for i in range(0, 10)]
for num in arr:
lists[num % (10 ** sorting) // (10 ** (sorting - 1))].append(num)
arr = []
for num_list in lists:
for num in num_list:
arr.append(num)
sorting += 1
return arr
if __name__ == '__main__':
# a = [4, 1, 3, 2, 16, 9, 10, 14, 8, 7]
a = [16, 14, 10, 9, 8, 7, 4, 3, 2, 1, 16, 14, 10, 9, 8, 7, 4, 3, 2, 1]
print(jishupaixu(a))