键索引计数法作为三种字符串排序算法中两种的基础,本身也很适用于小整数键的简单排序。
键索引计数法主要分为四步:统计频率,将频率转换为索引,数据分类,回写。
例如老师在统计学生的分数时可能会遇到以下数据处理问题。学生被分为若干班级,标号为1、2、3等。我们希望全校同学按班级分类,分类后每个班级中的姓名顺序与分类前的顺序相同(类似于从全校同学的成绩排名中抽离出每个班级同学的成绩排名),如图。因为班级的编号是较小的整数,使用键索引计数法来排序是很合适的。
为了说明这种方法,假设数组 a[]
中的每个元素都保存了一个名字和一个班级编号,总共N个,其中编号在0到R-1之间,代码 a[i].key()
会返回指定学生的编号。
统计频率
第一步是使用 int 数组 count[]
来统计所有键(班级编号)出现的频率。
注意这里每个班级下标加一对应的才是count数组的下标。这里下标进行了错位。
for(int i =0; i< N; i++){
count[a[i].key()+1]++;
}
由于班级的编号是 0 到 R-1 的数字,所以 count 数组的长度为 R+1。
将频率转换为索引
将频率转换为索引即将频率累加。
for(int r = 0; r< R; r++){
count[r+1] += count[r];
}
注意这里r 的判断条件为小于R,因为 count 的长度为 R+1,即最大下标为R。
数据分类
在将count数组转换为一张索引表后,将所有的元素(学生)移动到一个辅助数组aux[]
(aux是辅助的缩写)中以进行排序。每个元素在 aux[]
中的位置取决于它的键对应的 count[]
值。因为之前count的统计中做了错位,即 count 数组中下标为1 的值为0,即当第一个1班的同学被读到时,存入 aux[0]
的位置中,并且给 count 数组中该值自增,这样下一个1班的同学被读到时,count数组中下标为1的值对应为1了,将它存入 aux[1]
中。同理,如果读到了一个2班同学,在count[2]
中找到值为3,即将它存入 aux[3]中,并将count[2]
加一变为4。
for(int i = 0; i< N; i++){
aux[ count[a[i].key()]++ ] = a[i];
}
回写
即将aux中的元素转移到原数组a中。
最终代码:
int N = a.length;
int[] count = new int[R+1];
String[] aux = new String[N];
//统计频率:
for(int i = 0; i<N; i++){
count[a[i].key()+1]++;
}
//计算起始索引:
for(int r= 0; r < R; r++){
count[r+1] += count[r];
}
//数据分类:
for(int i = 0; i<N; i++){
aux[ count[ a[i].key() ]++ ] = a[i];
}
//回写:
for(int i = 0; i<N; i++){
a[i] = aux[i];
}
建索引计数法排序 N个 键为 0 到 R-1 之间的整数元素需要访问数组 11N + 4R + 1次,突破了 NlogN 的排序算法运行时间下限,只要当 R 和 N 的一个常数因子范围之内,它都是一个线性时间级别的排序算法。