概论
桶排序、计数排序、基数排序这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序:根据键值的每位数字来分配桶;
计数排序:每个桶只存储单一键值;
桶排序:每个桶存储一定范围的数值;
实现
一、基数排序
概念
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
动态演示图如下:
代码
def RadixSort(a):
max_len = len(str(max(a)))
new = [0] * len(a)
for i in range(max_len):
print(new)
x = 10 ** i
index = [0]*10
for w in range(len(a)):
index[a[w] // x % 10] += 1
for k in range(1, len(index)):
index[k] = index[k] + index[k - 1]
for j in range(len(a) - 1, -1, -1):
new[index[(a[j] // x % 10)]-1] = a[j]
index[(a[j] // x % 10)] -= 1
a = copy(new)
print(new)
二、计数排序
概念
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。
由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。
通俗地理解,例如有 10 个年龄不同的人,统计出有 8 个人的年龄比 A 小,那 A 的年龄就排在第 9 位,用这个方法可以得到其他每个人的位置,也就排好了序。当然,年龄有重复时需要特殊处理(保证稳定性),这就是为什么最后要反向填充目标数组,以及将每个数字的统计减去 1 的原因。
算法的步骤如下:
(1)找出待排序的数组中最大和最小的元素
(2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项
(3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
(4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
动态演示如下:
代码
def StackedCountSort(a):
b = min(a)
index = [0] * (max(a) - min(a) + 1)
new = [0] * len(a)
for i in range(len(a)):
index[a[i] - b] += 1
for x in range(1, len(index)):
index[x] = index[x] + index[x - 1]
for j in range(len(a) - 1, -1, -1):
new[index[a[j]-b]-1] = a[j]
index[a[j] - b] -= 1
print(new)
三、桶排序
概念
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
1.在额外空间充足的情况下,尽量增大桶的数量
2.使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中,同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
代码
def bucket_sort_simplify(arr, max_num):
"""
简化版
"""
buf = {i: [] for i in range(int(max_num)+1)} # 不能使用[[]]*(max+1),这样新建的空间中各个[]是共享内存的
arr_len = len(arr)
for i in range(arr_len):
num = arr[i]
buf[int(num)].append(num) # 将相应范围内的数据加入到[]中
arr = []
for i in range(len(buf)):
if buf[i]:
arr.extend(sorted(buf[i])) # 这里还需要对一个范围内的数据进行排序,然后再进行输出
return arr