键索引计数法
我们先介绍一种适合小整数键的简单排序方法,这是我们将要学习的字符串排序的基础,举个例子,我们希望将全班学生按组分类。如图
|
姓名 |
An |
Br |
Da |
Ga |
Ha |
Ja |
Jh |
Jn |
Ma |
|
组号 |
2 |
3 |
3 |
4 |
1 |
3 |
4 |
3 |
1 |
|
姓名 |
Mb |
Mi |
Mo |
Ro |
Sm |
Ta |
Ta |
Tp |
Wh |
|
组号 |
2 |
2 |
1 |
2 |
4 |
3 |
4 |
4 |
2 |
|
姓名 |
Wl |
Ws |
|
||||||
|
组号 |
3 |
4 |
|
||||||
我们这里用数组a[]来存储每个元素,其中每个元素都包含=一个名字和一个组号,a[i].key()返回元素的组号。
排序后的结果
|
姓名 |
Ha |
Ma |
Mo |
An |
Mb |
Mi |
Ro |
Wh |
Br |
|
组号 |
1 |
1 |
1 |
2 |
2 |
2 |
2 |
2 |
3 |
|
姓名 |
Da |
Ja |
Jn |
Ta |
Wl |
Ga |
Jh |
Sm |
Ta |
|
组号 |
3 |
3 |
3 |
3 |
3 |
4 |
4 |
4 |
4 |
|
姓名 |
Tp |
Ws |
|
||||||
|
组号 |
4 |
4 |
|
||||||
排序共分为四个步骤:
(一)频率统计
组号在0-R之间,键为组号,用一个int数组(初始全为0)统计每个键出现的频率,如果键为r,则count[r]++,但是在实际使用中我们用count[r+1]++,至于为什么将r加1,这是为了计算方便(下一步详细说明)
我i们先来进行统计:
An在第二组中,count[2+1]加1,count[3]==1继续扫描数组,Br在第三组,count[4]+1,count[4]==1,继续扫描,Da在第三组中,count[4]+1,count[4]==2……
扫描一遍数组得到count[0]=0,count[1]=0,count[2]=3,count[3]=5,count[4]=6,count[5]=6,即键1,2,3,4出现的次数分别为3,5,6,6次。
将频率转化为索引:
用count[]来计算每个键在排序结果中的索引位置,例如,第一组有三个人,第二组有5个人,那么第三组的同学在排序结果数组中的位置一定是8。
如图:下标从0开始
|
1 |
1 |
1 |
2 |
2 |
2 |
2 |
2 |
3(下标为8) |
…… |
即
键1下标开始count[1]=count[1]+count[0]=0
键2下标开始count[2]=count[2]+count[1]+count[0]=3.
键3下标开始count[3]=count[3]+count[2]+count[1]+count[0]=8
即对于每个键r,小于r+1的键的频率之和为小于r的键的频率之和在加上count[r]。
这样我们就能直观的看出来,计算某个键的起始位置,只需要计算count[这个键]+……count[0]即可,count[]数组的根本目的在于计算并存储索引位置,不在于存储键的频率。
数据分类
‘在将count[]数组转化为一张索引表后,我们将所有元素移到一个辅助数组aux[]中进行排序,每个元素在aux[]中的位置是由它的键(组号)决定的,在移动后将count[]中对应的元素加1,这个过程只需要遍历一遍数组即可完成。这种排序是稳定的。
for(int i=0;i<a.length;i++)
{ aux[count[a[i].key()]++]=a[i];
}
其中a[i].key()获取元素的组号,count[a[i].key()]++来保证下一个元素的索引位置。
步骤如图:
分类前:
aux[]
|
Count[1] |
|
|
Count[2] |
|
|
|
|
Count[3] |
|
|
|
|
|
Count[4] |
|
|
|
|
|
分类中:
|
1 |
Count[1 |
|
2 |
2 |
Count[2] |
|
|
3 |
3 |
3 |
3 |
Count[3] |
|
4 |
4 |
Count[4] |
|
|
|
分类后:
|
1 |
1 |
1 |
2 |
2 |
2 |
2 |
2 |
3 |
3 |
3 |
3 |
3 |
3 |
4 |
4 |
4 |
4 |
4 |
其中count[]指向3,count[2]指向8……
回写:
将辅助数组中的元素移动到原数组中
相关代码:
int N=a.length;
String aux[]=new String[N];
int[] count=new int[R+1];
//计算出现的次数
for(int i=;i<N;i++)
{
count[a[i].key()+1]++;
}
//将频率转化为索引
for(int r=0;r<R;r++)
{
count[r+1]+=count[r];
}
//将元素分类
for(int i=;i<N;i++)
{
aux[count[a[i].key()]++]=a[i];
}
//回写
for(int i=;i<N;i++)
{
a[i]=aux[i];
}
低优先的字符串排序
一般称为低位优先,对于一个字符串,从右向左扫描,这个方法依赖于我们上面介绍的键索引记数法,非常适合排序定长的字符串,比如身份证,车牌号,IP地址等。
代码实现:
1 public class LSD { 2 3 public static void sort(String[] a,int w) 4 { 5 int N=a.length; 6 int R=256;//使用扩展的ASCII字符集 7 String[] aux=new String[N]; 8 9 for(int d=w-1;d>=0;d--) 10 11 { 12 int[] count=new int[R+1]; 13 for(int i=0;i<N;i++) 14 { 15 16 count[a[i].charAt(d)+1]++; 17 } 18 19 for(int r=0;r<R;r++) 20 { 21 count[r+1]+=count[r]; 22 } 23 24 for(int i=0;i<N;i++) 25 { 26 aux[count[a[i].charAt(d)]++]=a[i]; 27 } 28 29 for(int i=0;i<N;i++) 30 { 31 a[i]=aux[i]; 32 } 33 } 34 } 35 36 public static void main(String[] args) { 37 String[] a= {"564","964","637","159"}; 38 System.out.println(a[1].charAt(2)); 39 sort(a,3); 40 for(String s:a) 41 { 42 System.out.println(s); 43 } 44 } 45 46 }