基于比较的排序算法,理论上无法突破(Ω(n \log n))时间复杂度下界 特殊场景下,不依赖元素间比较、利用元素结构特征,可实现线性时间排序
将元素视为由多个 “位”(digit)组成 按位进行多轮排序,每轮针对一个关键位 依赖稳定的子排序算法,保证先前排好的顺序不被打乱 关键保障:排序的稳定性(相同元素相对顺序不变)
按最低有效位(LSB)→ 最高有效位(MSB) 逐轮排序 第一轮:按个位数排序(0-9 分组) 后续轮次:依次按十位数、百位数等排序
初始序列:[329, 457, 657, 839, 436] 第一轮(按个位排序): 按个位 0-9 分组 → [436(6), 457(7), 657(7), 329(9), 839(9)] (注:457 与 657 个位相同,保持原顺序) 第二轮(按十位排序): 按十位 0-9 分组 → [329(2), 436(3), 839(3), 457(5), 657(5)] (注:436 与 839 十位相同,保持第一轮后的顺序) 第三轮(按百位排序): 按百位 0-9 分组 → [329(3), 436(4), 457(4), 657(6), 839(8)] (注:436 与 457 百位相同,保持第二轮后的顺序)
每轮排序通过稳定性保留前序结果,最终实现 “高位决定整体顺序”,完成正确排序。
每轮扫一遍序列O(n),总共轮数就是基数位数,设为k。 时间复杂度为O(kn),其中k一般不太大。
为了加深对基数排序的理解,我们通过一个编程任务来实践其实现。我们将以整数排序为例,并采用从最低有效位开始的方法对数字进行排序(LSD 基数排序)。
在实现过程中,我们需要编写一个按某个位数进行计数排序的辅助函数,然后在主函数中对每一位循环调用该辅助函数。
每选定一个基数后,需要用O(n)的时间复杂度将当前序列按照这个基数进行排序;或说找到按照当前基数下,当前序列的每一个数要前往的位置 这里用“计数排序”这个算法。 具体做法如下:
作为练习,[170, 45, 75, 90, 802, 24, 2, 66],按个位排序(exp=1)进行计数排序,请写下count数组的计数结果(2.步后)和前缀和结果。并表述在第4步的利用方式,从而得到序列结果。
请实现 radix_sort(arr) 函数,对传入的整数列表进行从小到大的排序。你可以假设所有整数为非负且位数相对固定。为了简化问题,我们以十进制为基数(基数=10)实现,并假定输入整数的最大位数为
从最低位(个位,exp=1)开始,对数组进行计数排序(Counting Sort),按该位的数值对元素排序。
然后依次向更高一位(exp=10,exp=100,...)进行排序,直到处理完最高位。
请在下方代码编辑区完成 counting_sort_by_digit 和 radix_sort 函数的实现。
import random
import time
def counting_sort_by_digit(arr, exp):
"""
对数组按某个数位进行计数排序(稳定的)。
参数:
arr: 要排序的数组
exp: 位标志,表示当前要按照 (exp位) 上的数值进行排序,
例如 exp=1 表示按个位排序,exp=10 表示按十位排序。
返回:
按指定位排序后的数组副本
"""
n = len(arr)
output = [0] * n # 存放排序结果
count = [0] * 10 # 计数数组(十进制位范围0-9)
# TODO: 计算每个数组元素在该位上的值,并计数
def radix_sort(arr):
"""
基数排序(按从低位到高位逐位排序)
参数:
arr: 待排序的整数列表
返回:
按非递减顺序排序的列表
"""
#TODO: 利用 counting_sort_by_digit 按位排序数组
#测试与性能分析
def measure(sort_func, data):
start = time.perf_counter_ns()
sort_func(data)
end = time.perf_counter_ns()
return (end - start) / 10**6 # 转换为毫秒
#生成随机测试数据
sizes = [10000, 50000, 100000]
print("基数排序性能测试:")
for n in sizes:
data = [random.randrange(0, 1000000) for _ in range(n)]
t = measure(radix_sort, data)
print(f"数据规模 n={n}: 排序耗时 {t:.2f} ms")
性能讨论
在完成代码并运行测试后,可以看到基数排序在不同规模输入下运行时间增长相对温和。理想情况下,对于固定位数
当
然而需注意,基数排序的实际常数因子不小(多次稳定排序和额外空间),对中等规模数据Python实现未必比C语言内建排序更快。
更重要的是,基数排序并非万能:如果元素位数
典型的线性时间排序算法 利用输入数据的分布特性(假设大致均匀分布在某范围)
第一步:创建若干 “桶”,将元素按值映射到对应桶中(实现粗分类)
第二步:对每个桶内元素单独排序(可选用任意排序算法)
第三步:按桶的顺序合并所有元素,得到有序序列
映射:将小数按值投入对应桶,每个桶仅覆盖原始区间 1/10,数据量少
桶内排序:因元素少,插入排序等简单算法足够快
合并:按桶序号收集,因 “i<j 时,第 i 号桶元素<第 j 号桶元素”,天然有序
使用桶排序算法对以下 8 个数字进行排序
[35, 12, 48, 27, 5, 39, 18, 42]
以48为分母,将其他所有数划分到0-0.2-0.4-0.6-0.8-1这5个桶中,输出每个桶里面的数字是什么。
请实现 bucket_sort(arr) 函数,将传入的[0,1)区间的小数数组排序。思路如下:
创建若干个空桶(列表),桶的数量可以根据数据规模n选择,这里取
将每个元素按照其值乘以桶数量后的整数部分,映射到对应的桶中。例如值为0.23、桶数为10时,
对每个非空桶内部进行排序。你可以直接使用 Python 内置排序(sorted)或实现简单排序算法。
按桶的顺序依次合并桶内元素,得到排序后的结果。
import random
import time
def bucket_sort(arr):
"""
桶排序(适用于0到1区间的小数)
参数:
arr: 介于[0,1)的浮点数列表
返回:
升序排序后的列表
"""
# TODO: 按原理实现桶排序
n = len(arr)
if n == 0:
return arr
# 1. 创建桶
# 2. 分配元素到各桶
# 3. 桶内排序
#简单测试与性能评估
sizes = [1000, 5000, 10000]
print("桶排序性能测试:")
for n in sizes:
#生成n个[0,1)均匀分布的小数
data = [random.random() for _ in range(n)]
start = time.perf_counter_ns()
sorted_data = bucket_sort(data)
end = time.perf_counter_ns()
elapsed = (end - start) / 10**6
#验证排序正确性
is_correct = "√" if sorted_data == sorted(data) else "×"
print(f"数据规模 n={n}: 排序耗时 {elapsed:.2f} ms, 排序正确性 {is_correct}")
前提:n 个元素、m 个桶,数据均匀分布 → 每个桶平均含 n/m 个元素 优化:选 m 接近 n 的量级 → 桶内排序成本视为常数 结果:整体期望运行时间为O(n)(线性时间)
极端场景:所有元素落入同一个桶 复杂度:取决于桶内排序算法(O (n log n) 或 O (n²))+ 分配桶的 O (n) 结果:退化至O (n log n) 甚至更差,失去线性时间优势 结论:理想分布下性能出众,数据分布不均时无优势
通过函数映射将元素分散到不同 “容器”(桶排序的 “桶”/ 哈希算法的 “哈希桶”),把原问题拆解为小的子问题
桶排序:用映射函数(如 index = int(num * bucket_count))划分元素所属桶,依据元素值分配
哈希算法:用哈希函数计算键的哈希桶索引,确定元素存储位置
这种映射也意味着都对分布均匀性的依赖
对比维度 桶排序的映射函数:一般直接做除法:保留元素大小顺序(按值范围划分) 哈希算法的哈希函数:随机映射函数,实现元素均匀分布,不能用于排序(除非特殊设计有序哈希)
桶排序可看作哈希思想在排序问题中的应用:均通过 “先分类、后处理” 的范式优化性能,但因目标不同,映射函数设计存在本质差异。