基数排序通过按位拆分待排序元素,逐位使用稳定的排序算法完成整体排序,计数排序因时间复杂度低、稳定性好,常被作为基数排序的子排序过程。当待排序数据为二进制字符串时,这种组合排序方式会出现一些容易被忽略的问题,需要针对性调整实现逻辑。

基数排序与计数排序基础原理
计数排序的核心逻辑是统计每个元素出现的次数,再通过前缀和计算元素的最终位置,适合取值范围较小的整数排序。基数排序则是从最低位到最高位依次对每个位执行计数排序,最终得到有序序列。
对于普通十进制整数,基数排序的实现逻辑如下:
# 计数排序实现,用于基数排序的子过程
def counting_sort(arr, exp):
n = len(arr)
output = [0] * n
count = [0] * 10 # 十进制每位取值范围是0-9
# 统计当前位每个数字出现的次数
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(arr):
max_val = max(arr)
exp = 1
# 从最低位到最高位依次排序
while max_val // exp > 0:
counting_sort(arr, exp)
exp *= 10
return arr
# 测试普通十进制整数排序
test_arr = [170, 45, 75, 90, 802, 24, 2, 66]
print(radix_sort(test_arr.copy()))
# 输出结果:[2, 24, 45, 66, 75, 90, 170, 802]
二进制字符串排序的常见陷阱
当待排序对象换成二进制字符串时,直接套用上述逻辑会出现多个问题,常见的陷阱主要有以下几类。
陷阱一:位处理顺序错误
二进制字符串的位权重是从左到右递增的,最高位在最左侧,最低位在最右侧。如果沿用十进制整数从最低位到最高位的排序顺序,会导致排序结果完全错误。
比如待排序二进制字符串列表为["101", "11", "100"],如果先从最低位(最右侧)开始排序,第一次排序后顺序可能变成["100", "101", "11"],最终排序结果会是["11", "100", "101"],但实际正确的二进制数值排序结果应该是["11", "100", "101"]吗?不对,二进制11是3,100是4,101是5,看似结果正确,但如果是["1001", "101", "10"]这样的长度不一致的串,问题就会暴露。
陷阱二:字符串长度不一致导致位缺失
二进制字符串长度不一致时,短字符串的高位可以视为0,但如果实现时没有补位,直接按字符串长度取位,会导致短字符串的高位被忽略,排序结果错误。
比如待排序列表["10", "100"],"10"是2,"100"是4,正确顺序应该是["10", "100"]。如果实现时按最大长度3位处理,"10"的高位补0后是"010",最低位是0,"100"最低位是0,第一次排序后顺序不变;处理中间位时"010"中间位是1,"100"中间位是0,排序后"100"在前,"010"在后;处理最高位时"010"最高位是0,"100"最高位是1,最终顺序还是["10", "100"],结果正确。但如果没有补位,直接按字符串长度取位,处理到第三位时"10"没有第三位,会被错误统计,导致排序偏差。
陷阱三:计数排序取值范围设置错误
二进制每一位只有0和1两种取值,计数排序的计数数组长度应该设为2,如果错误设为10(沿用十进制的设置),虽然不会报错,但会浪费空间,甚至如果实现逻辑有问题,可能导致统计错误。
二进制字符串排序的解决方案
针对上述问题,调整实现逻辑即可正确完成二进制字符串的排序,具体的解决步骤如下。
步骤一:统一字符串长度,高位补0
首先遍历所有二进制字符串,找到最长的字符串长度,然后将所有短字符串的高位补0,保证所有字符串长度一致,避免位缺失问题。
步骤二:调整位处理顺序,从最高位到最低位排序
二进制字符串的权重从左到右递增,因此需要从最高位(最左侧)开始,到最低位(最右侧)结束,依次执行计数排序,保证高位优先级高于低位。
步骤三:调整计数排序取值范围为2
二进制的每一位只有0和1两种取值,计数数组的长度设为2即可,减少不必要的空间占用。
完整实现代码示例
以下是修正后的二进制字符串基数排序实现:
# 针对二进制字符串的计数排序子过程,按指定位排序
def binary_counting_sort(arr, bit_index):
n = len(arr)
output = ["" for _ in range(n)]
# 二进制每一位只有0和1两种取值,计数数组长度为2
count = [0, 0]
# 统计当前位0和1出现的次数
for s in arr:
bit = int(s[bit_index])
count[bit] += 1
# 计算前缀和,count[0]表示位为0的元素个数,count[1]表示位为0和1的总个数
count[1] += count[0]
# 从后往前填充,保证排序稳定性
for i in range(n - 1, -1, -1):
bit = int(arr[i][bit_index])
output[count[bit] - 1] = arr[i]
count[bit] -= 1
return output
# 二进制字符串基数排序主函数
def binary_radix_sort(binary_strs):
if not binary_strs:
return binary_strs
# 找到最长字符串的长度
max_len = max(len(s) for s in binary_strs)
# 所有字符串高位补0,统一长度
padded_strs = [s.rjust(max_len, '0') for s in binary_strs]
# 从最高位(索引0)到最低位(索引max_len-1)依次排序
for i in range(max_len):
padded_strs = binary_counting_sort(padded_strs, i)
# 如果需要去掉补的0,可以按需处理,这里返回补0后的结果,也可根据需求调整
return padded_strs
# 测试代码
test_strs = ["101", "11", "100", "10", "1001"]
sorted_strs = binary_radix_sort(test_strs)
print("排序前:", test_strs)
print("排序后:", sorted_strs)
# 输出结果:
# 排序前: ['101', '11', '100', '10', '1001']
# 排序后: ['0010', '0011', '0100', '0101', '1001']
# 转换为数值验证:2,3,4,5,9,顺序正确
总结
基于计数排序的基数排序用于二进制字符串排序时,核心要注意位处理顺序、字符串长度统一、计数取值范围三个要点。只要按照最高位到最低位的顺序排序,提前补位统一长度,设置正确的计数数组大小,就能得到正确的排序结果。这种实现方式的时间复杂度为O(n*k),其中n是字符串数量,k是最大字符串长度,适合二进制字符串的批量排序场景。