介绍字符串的几种排序算法:键索引计数法、低位优先排序、高位优先排序、三向字符串快速排序。
键索引计数法
概念
键索引计数法:适合小整数键的简单排序。它的实现步骤为如下四步:
- 频率统计
- 频率转换为索引
- 数据分类
- 回写
算法实现
N
个字符串;R
个键值,键值范围 [0, R)
从 0 到 R-1
。
- 数组
a[]
存储待排序元素item
的数组,长度为N
,item
记录字符串和对应的键值。 - 整型数组
count[]
数组长度为R+1
,为什么要多两个?其中count[0]
和count[R]
有特殊作用:
在频率统计中,count[r]
记录键r-1
出现的频率,即从count[1]
开始记录键值出现的频率。其中:count[0]
无作用,默认设置为 0;count[R]
记录键R-1
出现的频率。
在频率转换为索引中,count[r]
记录键r
在预期排序结果的起始索引位置;任意给定键r
的起始索引位置为所有较小键出现频率之和,即count[r+1] += count[r]
。其中:count[0]
记录键 0 的起始索引;count[R]
无实际作用,记录a[]
的长度。
在数据分类中,count[r]
记录了*下一个键r
*的索引位置,即count[a[i].key()]++
。正常情况下,分类结束后的count[r]
和分类前的count[r+1]
值相等。 - 辅助数组
aux[]
存储排序后的值。a[i]
元素在aux[]
中的位置,由a[i]
的键值r
在count[]
中索引值决定,在移动之后count[]
中对应索引值加 1,确保count[r]
总是下一个键为r
的元素在aux[]
中的索引位置。
1 | // R 个键值,n 个字符串 |
图解
- 频率统计
- 频率转换为索引
- 数据分类
小结
键索引计数法是一种对于小整数键排序非常有效,但容易被忽略的排序算法。被排序字符串时等长字符串,可以在线性时间内完成排序。
注意:键索引计数法不会对相同键值的字符串做排序,仅仅按照键值排序排序字符串。
LSD
低位优先字符串排序
概念
LSD: Least Significant Digit radix sorts
,低位优先字符串排序算法,要求字符串时定长的(即所有被排序字符串长度相同),能够稳定地将定长字符串排序。
思路:从右向左(即低位优先)依次将字符作为键值排序,通常取扩展 ASCII
码长度作为键的个数(256 个)。定长字符串长度为 W
,只需要使用键索引计数法排序 W
遍,就可以得到最终排序结果了。
算法实现
1 | public static void sort(String[] a, int w) { |
参考:algs4: lsd
图解
小结
对于 N
个定长为 W
的字符串,无论 N
有多大,都只需要遍历 W
次就能实现排序,排序时间也是线性的。
MSD
高位优先字符串排序
概念
MSD: Most Significant Digit radix sorts
,高位优先字符串排序算法,可以实现通用字符串排序(即字符串长度可以不同),从左向右遍历所有字符。
思路:首先用键索引计数法将所有字符串按照首字母排序,然后(递归地)再将每个首字母所对应的子数组排序(忽略首字母,因为每一类中的所有字符串首字母是相同的)。
和快速排序一样,高位优先字符串排序会将数组切分为能够独立排序的子数组来完成排序任务,但它的切分会为每个首字母得到一个子数组,而不是像快速排序中产生固定的切分。
count[]
数组的含义
字符串末尾的约定:对于长度不等的字符串,当到达末尾时,指定位置为 -1,也就是说认为末尾字符为 count[0]
。
小型子数组
高位优先字符串排序的基本思想:在一般应用中,只需检查若干个字符就能完成所有字符串的排序。也就是说,这种方法能快速地将需要排序的数组切分为较小的数组。小型子数组对于高位优先的字符串排序性能至关重要。对于小型子数组,需要将排序算法切换为插入排序,减少过多的微型数组排序。
算法实现
1 | public class MSD { |
参考:algs4: msd
图解
小结
高位优先字符串排序不会处理等值键数组。如果相同的子字符串出现过多,也不会切换排序算法,那么递归方法会检查相同键中的每一个字符。总体来讲,高位优先算法能很好的解决随机字符串的排序。
三向字符串快速排序
概念
三向字符串快速排序 Three-way string quicksort
:根据键的首字母进行三向切分,仅在中间子数组中的下一个字符(因为键的首字母都与切分字符相等)继续递归排序。
小型子数组
在所有递归算法中,都可以通过对小型子数组进行特殊处理来提高效率。三向字符串快速排序和高位优先字符串排序一样,对于小型子数组会切换到插入排序算法。
算法实现
为了避免数组已经有序或接近有序的最坏情况,排序前先将数组随机打乱或者将第一个元素和一个随机位置的元素交换,以得到一个随机的切分元素。
1 | public class Quick3string { |
图解
小结
高位优先字符串排序可能会创建大量(空)子数组,而三向字符串快速排序的切分总是只有三个。因此三向字符串快速排序能够很好地处理等值键、有较长公共前缀的键、取值范围较小的键和小数组等,所有高位优先字符串排序算法不擅长的各种情况。和普通快速排序一样,三向字符串快速排序也不需要额外的空间(除了递归所需的隐式栈调用)。
小结
各种字符串排序算法的性能特点:
参考文档
- 《算法:第四版》 第 5 章