线性排序(Linear sort)
桶排序(Bucket sort)
基本思想:
- 将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。
- 桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。
- 桶排序常常用在外部排序[^1]中。
我们有 10 GB 的订单数据,我们希望按订单金额(假设金额都是正整数)进行排序,但是我们的内存有限,只有几百 MB,没办法一次性把 10 GB 的数据都加载到内存中。这个时候该怎么办?
解决方法:
-
我们可以先扫描一遍文件,看订单金额所处的数据范围。
假设经过扫描之后我们得到,订单金额最小是 1 元,最大是 10 万元。
-
我们将所有订单根据金额划分到 100 个桶里。
第一个桶我们存储金额在 1 元到 1000 元之内的订单,第二桶存储金额在 1001 元到 2000 元之内的订单,以此类推。每一个桶对应一个文件,并且按照金额范围的大小顺序编号命名(00,01,02…99)。
-
理想的情况下,如果订单金额在 1 到 10 万之间均匀分布,那订单会被均匀划分到 100 个文件中。
1)每个小文件中存储大约 100 MB 的订单数据,我们就可以将这 100 个小文件依次放到内存中,用快排来排序。
2)待所有文件都排好序之后,我们只需要按照文件编号,从小到大依次读取每个小文件中的订单数据,并将其写入到一个文件中,那这个文件中存储的就是按照金额从小到大排序的订单数据了。
-
非理想情况下,订单按照金额在 1 元到 10 万元之间并不一定是均匀分布的 。
有可能某个金额区间的数据特别多,划分之后对应的文件就会很大,没法一次性读入内存。
-
针对这些划分之后还是比较大的文件,我们可以继续划分。
比如,订单金额在 1 元到 1000 元之间的比较多,我们就将这个区间继续划分为 10 个小区间,1 元到 100 元,101 元到 200 元,201 元到 300 元…901 元到 1000 元。如果划分之后,101 元到 200 元之间的订单还是太多,无法一次性读入内存,那就继续再划分,直到所有的文件都能读入内存为止。
# bucket sort in Python
import math
def bucket_sort(order_data):
'''
使用桶排序算法对订单数据进行排序
参数:
order_data(list):待排序列表
返回值:
sorted_data(list): 已完成排序列表
'''
# 找到最大的订单金额
max_amount = max(order_data)
# 确定桶的数量
num_buckets = math.ceil(max_amount / 100)
# 创建空桶
buckets = [[] for _ in range(num_buckets)]
# 将订单数据放入对应的桶中
for amount in order_data:
# 划分为100个桶
bucket_index = amount // 100
buckets[bucket_index].append(amount)
# 对每个桶中的数据进行排序
for bucket in buckets:
bucket.sort()
# 合并所有桶的数据
sorted_data = []
for bucket in buckets:
sorted_data.extend(bucket)
return sorted_data
# 示例订单数据(假设为整数列表)
order_data = [102, 75, 158, 42, 95, 205, 300, 280, 150, 50, 180, 220, 500, 350]
# 使用桶排序进行排序
sorted_orders = bucket_sort(order_data)
# 打印排序结果
print(sorted_orders)
桶排序算法评价:
-
执行效率:最好情况时间复杂度为 O ( n + k ) O(n+k) O(n+k),最坏情况时间复杂度为 O ( n 2 ) O(n^2) O(n2),平均情况时间复杂度为 O ( n ) O(n) O(n)
-
内存消耗:是一个原地排序算法,空间复杂度为 O ( n + k ) O(n+k) O(n+k)
-
稳定性:是一个稳定的排序算法文章来源:https://www.toymoban.com/news/detail-480346.html
计数排序(Count Sort)
基本思想:
-
计数排序是一种特殊的桶排序
-
通过计算数组中每个唯一元素的出现次数来对数组的元素进行排序。
-
计数存储在辅助数组中,排序是通过将计数映射为辅助数组的索引来完成的
高考查分数的时候,系统会显示我们的成绩以及所在省的排名。如果你所在的省有 50 万考生,如何通过成绩快速排序得出名次呢?
解决方法:
- 考生的满分是 900 分,最小是 0 分,这个数据的范围很小,所以我们可以分成 901 个桶,对应分数从 0 分到 900 分。
- 根据考生的成绩,我们将这 50 万考生划分到这 901 个桶里。桶内的数据都是分数相同的考生,所以并不需要再进行排序。
- 我们只需要依次扫描每个桶,将桶内的考生依次输出到一个数组中,就实现了 50 万考生的排序。因为只涉及扫描遍历操作,所以时间复杂度是 O(n)。
# count sort in Python
def counting_sort(arr):
"""
计数排序,arr是数组。
假设数组中存储的都是非负整数
参数:
arr (List[int]): 输入的数组
返回值:
None: 原地排序,直接修改输入的数组arr
"""
n = len(arr)
if n <= 1:
return
# 查找数组中数据的范围
max_val = arr[0]
for i in range(1, n):
if max_val < arr[i]:
max_val = arr[i]
# 申请一个计数数组count,下标大小[0, max_val]
count = [0] * (max_val + 1)
# 统计每个元素在输入数组中出现的次数
for i in range(n):
count[arr[i]] += 1
# 依次累加
for i in range(1, max_val + 1):
# 帮助计算每个元素应该放置在排序结果数组中的正确索引位置
# count[i] 保存的值表示了输入数组中小于等于 i 的元素的个数
count[i] = count[i - 1] + count[i]
# 临时数组result,存储排序之后的结果
result = [0] * n
# 从输入数组的最后一个元素开始向前遍历,即从右往左,从后往前遍历。
for i in range(n - 1, -1, -1):
index = count[arr[i]] - 1
result[index] = arr[i]
count[arr[i]] -= 1
# 将结果拷贝给arr数组
for i in range(n):
arr[i] = result[i]
计数排序算法评价:
-
执行效率:最好情况时间复杂度为 O ( n + k ) O(n+k) O(n+k),最坏情况时间复杂度为 O ( n + k ) O(n+k) O(n+k),平均情况时间复杂度为 O ( n + k ) O(n+k) O(n+k)
-
内存消耗:是一个原地排序算法,空间复杂度为 O ( N ) O(N) O(N)
-
稳定性:是一个稳定的排序算法
注意:
-
计数排序只能用在数据范围不大的场景中,如果数据范围 K 比要排序的数据 n 大很多,就不适合用计数排序了
-
计数排序只能给非负整数排序,如果要排序的数据是其他类型的,要将其在不改变相对大小的情况下,转化为非负整数
基数排序(Radix sort)
基本思想:
- 首先对具有相同位值的各个数字进行分组来对元素进行排序。
- 然后,根据元素的递增/递减顺序对元素进行排序。
# Radix sort in Python
def countingSort(array, place):
'''
使用计数排序根据位数对元素进行排序。
参数:
array (List[int]): 输入的数组。
place (int): 当前位数。
返回值:
None
'''
size = len(array)
output = [0] * size
count = [0] * 10
# # 统计每个数字出现的次数
for i in range(0, size):
index = array[i] // place
count[index % 10] += 1
# 计算数字累计出现的次数
for i in range(1, 10):
count[i] += count[i - 1]
# 按照位数顺序将元素放置到输出数组中
i = size - 1
while i >= 0:
index = array[i] // place
output[count[index % 10] - 1] = array[i]
count[index % 10] -= 1
i -= 1
# 将输出数组复制回原始数组
for i in range(0, size):
array[i] = output[i]
def radixSort(array):
"""
实现基数排序的主函数。
参数:
array (List[int]): 输入的数组。
返回值:
None
"""
max_element = max(array)
# 根据数字的位数进行计数排序
place = 1
while max_element // place > 0:
countingSort(array, place)
place *= 10
data = [121, 432, 564, 23, 1, 45, 788]
radixSort(data)
print(data)
基数排序算法评价:
-
执行效率:最好情况时间复杂度为 O ( n + k ) O(n+k) O(n+k),最坏情况时间复杂度为 O ( n + k ) O(n+k) O(n+k),平均情况时间复杂度为 O ( n + k ) O(n+k) O(n+k)
-
内存消耗:是一个原地排序算法,空间复杂度为 O ( N ) O(N) O(N)
-
稳定性:是一个稳定的排序算法
注意:文章来源地址https://www.toymoban.com/news/detail-480346.html
- 基数排序对要排序的数据是有要求的,需要可以分割出独立的“位”来比较,而且位之间有递进的关系,如果 a 数据的高位比 b 数据大,那剩下的低位就不用比较了。
- 除此之外,每一位的数据范围不能太大,要可以用线性排序算法来排序,否则,基数排序的时间复杂度就无法做到 O(n) 了
参考文献
- 排序(下):如何用快排思想在O(n)内查找第K大元素?极客时间. 2018
- https://www.programiz.com/dsa/radix-sort
到了这里,关于数据结构与算法之美 | 排序(3)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!