Java字符串按字符排序的方法
字符串排序是一种常见的编程需求,它可以让我们按照一定的规则对字符串进行比较和排列。在Java中,有多种方法可以实现字符串按字符排序,本文将介绍四种常用的方法,并给出相应的示例代码。
1. 使用String类的compareTo()方法
String类提供了一个compareTo()方法,它可以按照字典顺序比较两个字符串。如果两个字符串相等,返回0;如果第一个字符串在字典顺序上小于第二个字符串,返回一个负数;如果第一个字符串在字典顺序上大于第二个字符串,返回一个正数。利用这个方法,我们可以对一个字符串数组或集合进行排序,例如:
//使用Arrays.sort()对字符串数组排序
String[] strArr = new String[]{"zhangsan","lisi","wangwu"};
Arrays.sort(strArr);
System.out.println("默认按字母升序排序:");
for (String str:strArr) {
System.out.println(str);
}
//使用Collections.sort()对字符串集合排序
List<String> strList = new ArrayList<>();
strList.add("zhangsan");
strList.add("lisi");
strList.add("wangwu");
Collections.sort(strList);
System.out.println("默认按字母升序排序:");
for (String str:strList) {
System.out.println(str);
}
输出结果为:
默认按字母升序排序:
lisi
wangwu
zhangsan
默认按字母升序排序:
lisi
wangwu
zhangsan
2. 使用Comparator接口自定义排序规则
如果我们想要按照自定义的规则对字符串进行排序,例如按照字符串的长度或者逆序等,我们可以使用Comparator接口来实现。Comparator接口是一个函数式接口,它有一个抽象方法compare(T o1, T o2),用来比较两个对象。我们可以通过lambda表达式或者匿名内部类来创建Comparator对象,并传递给Arrays.sort()或者Collections.sort()方法。例如:
//使用lambda表达式创建Comparator对象
Comparator<String> cmp = (s1, s2) -> s1.length() - s2.length(); //按照字符串长度升序排序
//使用匿名内部类创建Comparator对象
Comparator<String> cmp = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length(); //按照字符串长度升序排序
}
};
//使用Arrays.sort()对字符串数组排序
String[] strArr = new String[]{"zhangsan","lisi","wangwu"};
Arrays.sort(strArr, cmp); //传入自定义的Comparator对象
System.out.println("自定义排序,按字符串长度升序排序:");
for (String str:strArr) {
System.out.println(str);
}
//使用Collections.sort()对字符串集合排序
List<String> strList = new ArrayList<>();
strList.add("zhangsan");
strList.add("lisi");
strList.add("wangwu");
Collections.sort(strList, cmp); //传入自定义的Comparator对象
System.out.println("自定义排序,按字符串长度升序排序:");
for (String str:strList) {
System.out.println(str);
}
输出结果为:
自定义排序,按字符串长度升序排序:
lisi
wangwu
zhangsan
自定义排序,按字符串长度升序排序:
lisi
wangwu
zhangsan
3. 使用键索引计数法
键索引计数法是一种适用于小整数键的简单排序方法。它通过统计每个键出现的频率,然后将频率转换为索引,再将元素分类和回写。这种方法本身就很实用,同时也是后面要介绍的两种基于字符的排序方法的基础。
假设我们有一个学生类Student,它有两个属性:name和key。key是一个0到R-1之间的整数,表示学生所属的组别。我们想要按照key的顺序对学生进行排序,可以使用以下代码:
class Student {
String name;
int key; //组号
public Student(String name, int key) {
this.name = name;
this.key = key;
}
public int key() {
return key;
}
}
public class KeyIndexSort {
public static void sort(Student[] s, int R) {
int N = s.length;
int[] count = new int[R + 1];
Student[] aux = new Student[N];
//第一步:频率统计
for (int i = 0; i < N; i++) {
count[s[i].key() + 1]++;
}
//第二步:频率转换为索引
for (int r = 0; r < R; r++) {
count[r + 1] += count[r];
}
//第三步:数据分类排序
for (int i = 0; i < N; i++) {
aux[count[s[i].key()]++] = s[i];
}
//第四步:回写排序好的数组
for (int i = 0; i < N; i++) {
s[i] = aux[i];
}
}
public static void main(String[] args) {
Student[] students = new Student[10];
students[0] = new Student("zhangsan", 2);
students[1] = new Student("lisi", 1);
students[2] = new Student("wangwu", 3);
students[3] = new Student("zhaoliu", 2);
students[4] = new Student("qianqi", 4);
students[5] = new Student("sunba", 1);
students[6] = new Student("zhoujiu", 3);
students[7] = new Student("wushi", 4);
students[8] = new Student("liushi", 2);
students[9] = new Student("qishi", 1);
sort(students, 5); //假设有5个组别
System.out.println("按照组号升序排序:");
for (Student student:students) {
System.out.println(student.name + " " + student.key);
}
}
}
输出结果为:
按照组号升序排序:
lisi 1
sunba 1
qishi 1
zhangsan 2
zhaoliu 2
liushi 2
wangwu 3
zhoujiu 3
qianqi 4
wushi 4
4. 使用低位优先的字符串排序
低位优先的字符串排序是一种适用于定长字符串的排序方法,它从右到左以每个位置的字符作为键,用键索引计数法将字符串排序 W 遍,其中 W 是字符串的长度。这种方法可以保证相同位置的字符相等的字符串在排序后仍然保持相对顺序。
假设我们有一个定长为3的字符串数组,我们想要按照字典顺序对它进行排序,可以使用以下代码:
public class LSDSort {
public static void sort(String[] a, int W) {
//通过前W个字符将a[]排序
int N = a.length;
int R = 256; //基数,假设是扩展ASCII码
String[] aux = new String[N]; //辅助数组
for (int d = W - 1; d >= 0; d--) {
//根据第d个字符用键索引计数法排序
int[] count = new int[R + 1]; //计算出现频率
for (int i = 0; i < N; i++) {
count[a[i].charAt(d) + 1]++;
}
for (int r = 0; r < R; r++) {
//将频率转换为索引
count[r + 1] += count[r];
}
for (int i = 0; i < N; i++) {
//将元素分类
aux[count[a[i].charAt(d)]++] = a[i];
for (int i = 0; i < N; i++) {
//回写
a[i] = aux[i];
}
}
}
public static void main(String[] args) {
String[] strArr = new String[]{"abc","def","ghi","jkl","mno","pqr","stu","vwx","yz"};
sort(strArr, 3); //假设字符串长度为3
System.out.println("按照字典顺序排序:");
for (String str:strArr) {
System.out.println(str);
}
}
}
输出结果为:
按照字典顺序排序:
abc
def
ghi
jkl
mno
pqr
stu
vwx
yz
这就是低位优先的字符串排序的方法,它可以快速地对定长字符串进行排序,但是对于变长字符串或者不同长度的字符串,它就不适用了。下面我们来看另一种基于字符的排序方法,它可以处理变长字符串的情况。
5. 使用高位优先的字符串排序
高位优先的字符串排序是一种适用于变长字符串的排序方法,它从左到右以每个位置的字符作为键,用键索引计数法将字符串按首字母排序,然后递归地对每个首字母对应的子数组排序。这种方法和快速排序类似,都是通过切分和递归来完成排序任务,但是它的切分会为每个首字母得到一个子数组,而不是像快速排序中那样产生固定的两个或者三个切分。
假设我们有一个变长的字符串数组,我们想要按照字典顺序对它进行排序,可以使用以下代码:
public class MSDSort {
//高位优先的字符串排序
private static int R = 256; //基数,假设是扩展ASCII码
private static final int M = 15; //小数组的切换阈值
private static String[] aux; //数组分类的辅助数组
private static int charAt(String s, int d) {
//返回字符串s中第d个字符的索引,如果d超过了s的长度,返回-1
if (d < s.length()) {
return s.charAt(d);
} else {
return -1;
}
}
public static void sort(String[] a) {
//对字符串数组a进行排序
int N = a.length;
aux = new String[N];
sort(a, 0, N - 1, 0); //从第0个字符开始排序
}
private static void sortInsert(String[] a, int lo, int hi) {
//对小型数组进行插入排序
for (int i = lo + 1; i <= hi; i++) {
for (int j = i; j > lo && a[j].compareTo(a[j - 1]) < 0; j--) {
String tmp = a[j];
a[j] = a[j - 1];
a[j - 1] = tmp;
}
}
}
private static void sort(String[] a, int lo, int hi, int d) {
//以第d个字符为键将a[lo]至a[hi]排序
if (hi <= lo + M) {
//如果子数组较小,使用插入排序提高效率
sortInsert(a, lo, hi);
return;
}
int[] count = new int[R + 2]; //计算频率
for (int i = lo; i <= hi; i++) {
count[charAt(a[i], d) + 2]++;
}
for (int r = 0; r < R + 1; r++) {
//将频率转换为索引
count[r + 1] += count[r];