前言
“排序”这两个字想必大家如果学过算法的话恐怕都不会陌生。但是我们在算法的学习中往往都是用一个整数数组来进行排序,而在实际工作中,我们往往碰到的都是实体类集合或者Map集合的形式。那么,在这种情况下,我们该如何进行排序呢?
前期准备
实体类
我们先来准备一个实体类,待会用于生成实体类集合再排序:
package listsort;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* @ClassName Entity
* @Description 实体类 - 人类
* @Author 古阙月
* @Date 2020/12/27
* @Version 1.0
*/
//lombok插件的注解
@Data // 若未使用lombok插件,请自行生成getter、setter以及toString方法
@AllArgsConstructor // 若未使用lombok插件,请自行生成有参构造方法
@NoArgsConstructor // 若未使用lombok插件,请自行生成无参构造方法
@Accessors(chain = true) // 开启链式编程
public class Person {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
}
生成集合工具类
再来一个可以批量生成集合的工具类:
package listsort;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName GainList
* @Description 获取集合的工具类
* @Author 古阙月
* @Date 2020/12/27 23:43
* @Version 1.0
*/
public class GainList {
/**
* 随机生成包含num个map的List集合
* @param num
* @return
*/
public static List<Map> getMapList(int num) {
List<Map> mapList = new ArrayList<>();
for (int i = 0; i < num; i++) {
// 随机生成 0-100 直接的整数
int j = (int) (Math.random() * 101);
Map map = new HashMap();
map.put("name", "李华"+j);
map.put("age", j);
mapList.add(map);
}
return mapList;
}
/**
* 随机生成包含num个人类的List集合
* @param num
* @return
*/
public static List<Person> getPersonList(int num) {
List<Person> personList = new ArrayList<>();
for (int i = 0; i < num; i++) {
// 随机生成 0-100 直接的整数
int j = (int) (Math.random() * 101);
Person person = new Person();
person.setName("李华"+j);
person.setAge(j);
personList.add(person);
}
return personList;
}
}
方式一
实体类集合排序
如果让你们将一个包含实体类人类的集合按照年龄升序排序,你们会怎么做?
可能很多小伙伴会像下面的代码一样先得到一个年龄升序的数组(反正我之前就是这么做的,结果挨批了),然后再根据这个年龄数组将集合中的实体类一个个抽出来:
package listsort;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @ClassName ListEntitySort
* @Description List<entity> 排序 - 老方法
* @Author 古阙月
* @Date 2020/12/25
* @Version 1.0
*/
public class ListEntitySort {
public static void main(String[] args) {
// 用于得到排序后返回的list
List<Person> sortList = new ArrayList<>();
/*
* 随机生成包含3个人类的List集合
*/
List<Person> personList = GainList.getPersonList(3);
System.out.println("排序前:" + personList);
// 用于获取年龄数组
int[] ageArr = new int[personList.size()];
// 遍历集合,获取包含年龄的数组
for (int i = 0; i < personList.size(); i++) {
ageArr[i] = personList.get(i).getAge();
}
// 将包含年龄的数组排序 - 升序
Arrays.sort(ageArr);
for (int i = 0; i < ageArr.length; i++) {
for (int j = 0; j < personList.size(); j++) {
if (ageArr[i] == personList.get(j).getAge()) {
// 添加
sortList.add(personList.get(j));
// 移除,减少下次遍历次数
personList.remove(j);
// 防止报错
j--;
}
}
}
System.out.println("排序后:" + sortList);
}
}
运行得:
Map集合排序
Map集合排序的方式一其实跟实体类集合是一样的,这里我们来个降序排序:
package listsort;
import java.util.*;
/**
* @ClassName ListMapSort
* @Description List<map> 排序 - 老方法
* @Author 古阙月
* @Date 2020/12/25 16:40
* @Version 1.0
*/
public class ListMapSort {
public static void main(String[] args) {
// 用于得到排序后返回的list
List<Map> sortList = new ArrayList<>();
/*
* 随机生成包含3个map的List集合
*/
List<Map> mapList = GainList.getMapList(3);
System.out.println("排序前:" + mapList);
// 用于获取年龄数组
Integer[] ageArr = new Integer[mapList.size()];
// 遍历集合,获取 包含年龄的数组
for (int i = 0; i < mapList.size(); i++) {
ageArr[i] = (int) mapList.get(i).get("age");
}
// 将包含年龄的数组排序 - 降序
Arrays.sort(ageArr, Collections.reverseOrder());
for (int i = 0; i < ageArr.length; i++) {
for (int j = 0; j < mapList.size(); j++) {
if (ageArr[i] == (int)mapList.get(j).get("age")) {
// 添加
sortList.add(mapList.get(j));
// 移除,减少下次遍历次数
mapList.remove(j);
// 防止报错
j--;
}
}
}
System.out.println("排序后:" + sortList);
}
}
运行得:
方式二
之前方式一的方式虽然可以实现效果,但是我们可以很容易发现两大缺点:
- 代码量太多导致可读性较差,不易于维护。
- 空间复杂度较高,new了一个新的集合。
那么有没有什么方法可以改进呢? 当然有了,不然我写这篇博客干嘛?
Java的api其实已经给我们提供方法了,如以实体类集合排序为例:
package listsort;
import java.util.*;
/**
* @ClassName ListEntitySort2
* @Description List<entity> 排序
* @Author 古阙月
* @Date 2020/12/25
* @Version 1.0
*/
public class ListEntitySort2 {
public static void main(String[] args) {
/*
* 随机生成包含3个人类的List集合
*/
List<Person> personList = GainList.getPersonList(3);
System.out.println("排序前:" + personList);
/**
* 将包含3个person实体的List集合按照年龄由低到高排序
*/
Collections.sort(personList, new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
int age1 = p1.getAge();
int age2 = p2.getAge();
if (age1 > age2) return 1;
if (age1 < age2) return -1;
return 0;
}
});
System.out.println("排序后:" + personList);
}
}
是不是感觉代码瞬间简洁了许多?运行得:
排序完成,very good!
比较
通过以上对比,我们可以很明显的发现方式二的两大优点:
- 代码可读性较好,易于维护。
- 空间复杂度较低。
那么,既然是程序,必定逃不过一个问题,那就是速率问题,或者说是时间复杂度。
让我们来测试一下吧!!!
我们以Map集合排序为例,如用方式一对1000个Map进行排序:
package listsort;
import java.util.*;
/**
* @ClassName ListMapSort
* @Description List<map> 排序 - 老方法
* @Author 古阙月
* @Date 2020/12/25 16:40
* @Version 1.0
*/
public class ListMapSort {
public static void main(String[] args) {
// 获取当前系统的毫秒数
long begin = System.currentTimeMillis();
// 用于得到排序后返回的list
List<Map> sortList = new ArrayList<>();
/*
* 随机生成包含num个map的List集合
*/
int num = 1000;
List<Map> mapList = GainList.getMapList(num);
System.out.println("排序前:" + mapList);
// 用于获取年龄数组
int[] ageArr = new int[mapList.size()];
// 遍历集合,获取 包含年龄的数组
for (int i = 0; i < mapList.size(); i++) {
ageArr[i] = (int) mapList.get(i).get("age");
}
// 将包含年龄的数组排序 - 升序
Arrays.sort(ageArr);
for (int i = 0; i < ageArr.length; i++) {
for (int j = 0; j < mapList.size(); j++) {
if (ageArr[i] == (int)mapList.get(j).get("age")) {
// 添加
sortList.add(mapList.get(j));
// 移除,减少下次遍历次数
mapList.remove(j);
j--; // 防止报错
}
}
}
System.out.println("排序后:" + sortList);
// 获取当前系统的毫秒数
long end = System.currentTimeMillis();
System.out.println("方法一排序含" + num + "个map的List集合,耗时:" + (double)(end - begin) / 1000 + "s");
}
}
运行得:
我们可以发现一号选手用时0.048秒,下面有请二号选手:
package listsort;
import java.util.*;
/**
* @ClassName ListMapSort2
* @Description List<map> 排序
* @Author 古阙月
* @Date 2020/12/25
* @Version 1.0
*/
public class ListMapSort2 {
public static void main(String[] args) {
long begin = System.currentTimeMillis();
/*
* 随机生成包含num个map的List集合
*/
int num = 1000;
List<Map> mapList = GainList.getMapList(num);
System.out.println("排序前:" + mapList);
/**
* 将包含3个map的List集合按照年龄由低到高排序
*/
Collections.sort(mapList, new Comparator<Map>() {
@Override
public int compare(Map o1, Map o2) {
int age1 = (int) o1.get("age");
int age2 = (int) o2.get("age");
if (age1 > age2) return 1;
if (age1 < age2) return -1;
return 0;
}
});
System.out.println("排序后:" + mapList);
long end = System.currentTimeMillis();
System.out.println("方法一排序含" + num + "个map的List集合,耗时:" + (double)(end - begin) / 1000 + "s");
}
}
运行得:
这个时候,我们惊喜的发现,二号选手用时更短,仅为0.015秒,不信你多测几次便知。
哪怕,我们对方式一进行优化,如将 包含年龄的数组排序 的排序方法这一行代码
// 将包含年龄的数组排序 - 升序
Arrays.sort(ageArr);
改为快速排序:
// 快速排序
QuickSort.quickSort(ageArr, 0, ageArr.length - 1);
虽然速率有所波动,但有时耗时会很少,有时会更多,效果仍是差强人意。所以这里我们可以发现方式二的第三大优点:
- 时间复杂度低,也就是速率比较快。
至于快速排序速率为什么会波动以及快速排序的代码,大家可以参考我的下面这篇博客中的第四章节<快速排序>,然后自行探究验证:
肝了几万字,送给看了《算法图解》却是主攻Java的你和我(上篇)