pythondef counting_sort_by_digit(arr, exp): """根据指定的位(exp=1,10,100...)对数组进行计数排序""" n = len(arr) output = [0] * n # 输出数组 count = [0] * 10 # 计数数组,0-9共10个数字 # 统计当前位上每个数字出现的次数 for i in range(n): index = (arr[i] // exp) % 10 # 提取当前位上的数字 count[index] += 1 # 将计数转换为累积计数(决定每个数字在输出数组中的位置) for i in range(1, 10): count[i] += count[i - 1] # 从后向前遍历,确保稳定性(相同数字不会乱序) for i in range(n - 1, -1, -1): index = (arr[i] // exp) % 10 output[count[index] - 1] = arr[i] count[index] -= 1 # 将排序后的结果复制回原数组 for i in range(n): arr[i] = output[i]def radix_sort_positive(arr): """对非负整数数组进行基数排序""" if len(arr) == 0: return arr # 找到最大数字,确定位数 max_num = max(arr) exp = 1 # 从个位开始(10^0) # 当最大数字还有更高位时继续循环 while max_num // exp > 0: counting_sort_by_digit(arr, exp) exp *= 10 # 前进到下一位 return arr# 测试代码test_arr = [170, 45, 75, 90, 802, 24, 2, 66]print("原始数组:", test_arr)sorted_arr = radix_sort_positive(test_arr)print("排序后数组:", sorted_arr)代码解析:- counting_sort_by_digit 是核心辅助函数,它基于当前位(个位、十位…)进行计数排序- 使用倒序遍历保证了排序的稳定性——相同数字的相对顺序不变- 外层循环 while max_num // exp > 0 控制位数,直到最大数字的最高位处理完毕运行这段代码,你会看到:原始数组: [170, 45, 75, 90, 802, 24, 2, 66]排序后数组: [2, 24, 45, 66, 75, 90, 170, 802]## 4. 代码示例二:支持负数的改进版实际应用中,数组经常包含负数。上面的基础版无法处理负数,因为负数的数字位提取会出问题。下面我们扩展一下,让基数排序支持负数:pythondef radix_sort_with_negative(arr): """ 支持负数的基数排序 思路:将负数单独分离,取绝对值排序后取反,再与正数合并 """ if len(arr) <= 1: return arr # 分离正负数 negatives = [-x for x in arr if x < 0] # 负数取绝对值 positives = [x for x in arr if x >= 0] # 对正数部分进行基数排序 if positives: max_positive = max(positives) exp = 1 while max_positive // exp > 0: counting_sort_by_digit(positives, exp) exp *= 10 # 对负数部分(绝对值)进行基数排序 if negatives: max_negative = max(negatives) exp = 1 while max_negative // exp > 0: counting_sort_by_digit(negatives, exp) exp *= 10 # 负数排序后要反转(因为绝对值越大,原数越小) # 例如:[-5, -3] -> 绝对值[5, 3]排序后->[3, 5] -> 取反->[-3, -5] -> 反转->[-5, -3] negatives_sorted = [-x for x in negatives][::-1] # 合并结果:负数在前,正数在后 return negatives_sorted + positives# 测试包含负数的数组test_arr2 = [3, -1, 0, -5, 8, -3, 2, -8]print("原始数组(含负数):", test_arr2)sorted_arr2 = radix_sort_with_negative(test_arr2)print("排序后数组:", sorted_arr2)代码解析:- 核心技巧:将负数转换为正数(取绝对值)处理,排序后再转换回来- 负数部分排序后需要反转:因为绝对值越大,原数越小(例如 -8 < -3)- 最终合并时负数在前(升序排列),正数在后运行结果:原始数组(含负数): [3, -1, 0, -5, 8, -3, 2, -8]排序后数组: [-8, -5, -3, -1, 0, 2, 3, 8]## 5. 性能分析与应用场景### 时间复杂度- 最好情况:O(d * (n + k))- 最坏情况:O(d * (n + k)) - 平均情况:O(d * (n + k))其中:- n = 元素个数- d = 最大位数(如3位数的d=3)- k = 基数(十进制为10)注意:当d较小(比如固定长度整数)时,基数排序的时间复杂度接近O(n),比O(n log n)的快速排序更快。### 空间复杂度- O(n + k),需要额外的计数数组和输出数组### 与比较排序的对比| 算法 | 时间复杂度 | 空间复杂度 | 稳定性 ||------|------------|------------|--------|| 快速排序 | O(n log n) | O(log n) | 不稳定 || 归并排序 | O(n log n) | O(n) | 稳定 || 基数排序 | O(d * (n+k)) | O(n+k) | 稳定 |### 适用场景1. 固定位数的整数排序:如电话号码、身份证号、邮政编码2. 数据范围较大但位数有限:比如10万个数,范围0-10^9,但只有10位数字3. 需要稳定排序:比如多关键字排序(先按年龄排,再按姓名排)### 不适用场景1. 浮点数:位数不固定,且基数处理复杂(可以通过乘以10的幂次转换为整数)2. 字符串:需要按字符编码处理,但长度不一3. 数据位数差异极大:比如1和1000000000,会导致大量的空桶操作## 6. 总结基数排序是一种优雅而高效的排序算法,它通过“按位分配”巧妙绕开了元素比较的瓶颈。从算法原理上看,它体现了分治思想:将一个复杂问题(多位数排序)分解为多个简单问题(单数字排序)。从实现角度看,它依赖计数排序作为子过程,利用了计数排序的稳定性和线性时间复杂度。虽然基数排序不适用于所有场景(比如浮点数或字符串),但在处理固定位数的整数时,它往往能展现出惊人的速度——特别是当数据量很大而位数较小的时候。结合代码示例,我们可以看到基数排序的实现并不复杂,而且通过一些技巧(如处理负数),它可以适应更广泛的应用场景。最后,记住基数排序的核心口诀:“从低位到高位,分桶收集稳又好”。理解了这个思想,你就能轻松掌握这种非比较型排序的精髓。
4367

被折叠的 条评论
为什么被折叠?
