最近在项目中开发了一个组织树的功能,正好使用了深度优先遍历和广度优先遍历进行结点遍历,觉得非常有意思这里做一下笔记记录一下。
提示:部门表设计可以查看以下链接:
MYSQL8使用CTE实现递归遍历
深度优先遍历全称叫Depth First Search(简称DFS)
,例如查询一棵树从顶级节点往下找所有子节点,深度优先遍历会一条路走到底,直到遇到“死胡同”才会返回到i上一节点(回溯)
,如果上一节点没有其他路可以探索,则会继续原路返回探索别的路线,直到遍历完最后退出
。
组织架构功能,公司下还有多个部门,每个部门下有对应的员工,要求展示公司部门关系以及部门与员工的关系,要求将整个公司的组织架构下所有的结构和员工返回,一个公司组织架构如下:
先构建部门树形关系,再查询所有员工根据部门进行分组,使用深度优先遍历DFS挂载每个部门下的所有员工。以下是部门和员工表结构设计和表数据:
CREATE TABLE `user_info` (
`id` bigint NOT NULL COMMENT '主键ID',
`name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '姓名',
`department_id` bigint NULL DEFAULT 0 COMMENT '部门id',
`delete_flag` int NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user_info
-- ----------------------------
INSERT INTO `user_info` VALUES (0, 'Jerry', 4, 0);
INSERT INTO `user_info` VALUES (1, 'Jone', 1, 0);
INSERT INTO `user_info` VALUES (2, 'Jack', 1, 0);
INSERT INTO `user_info` VALUES (3, 'Tom', 2, 0);
INSERT INTO `user_info` VALUES (4, 'Sandy', 3, 0);
INSERT INTO `user_info` VALUES (5, 'Billie', 4, 0);
SET FOREIGN_KEY_CHECKS = 1;
CREATE TABLE `department` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '部门名称',
`parent_id` bigint NULL DEFAULT NULL,
`create_time` datetime(0) NULL DEFAULT NULL,
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
`delete_flag` int NULL DEFAULT 0 COMMENT '1无效 0有效',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of department
-- ----------------------------
INSERT INTO `department` VALUES (1, '我的公司', NULL, '2022-04-23 22:38:49', '2022-04-23 23:03:55', 0);
INSERT INTO `department` VALUES (2, '技术部', 1, '2022-04-23 23:04:00', '2022-04-23 23:04:03', 0);
INSERT INTO `department` VALUES (3, '财务部', 1, '2022-04-23 23:17:19', '2022-04-23 23:17:22', 0);
INSERT INTO `department` VALUES (4, '大数据部', 2, '2022-04-23 23:30:26', '2022-04-23 23:30:28', 0);
INSERT INTO `department` VALUES (5, '商品部', 2, '2022-04-23 23:30:50', '2022-04-23 23:30:51', 0);
INSERT INTO `department` VALUES (6, '工程部', 1, '2022-04-23 23:31:12', '2022-04-23 23:31:14', 0);
INSERT INTO `department` VALUES (7, '测试部', 2, '2022-04-23 23:31:34', '2022-04-23 23:31:36', 0);
INSERT INTO `department` VALUES (8, '大数据研发一部', 4, '2022-04-23 23:55:05', '2022-04-24 22:34:17', 0);
INSERT INTO `department` VALUES (9, '大数据开发一部', 4, '2022-04-24 22:34:25', '2022-04-24 22:34:27', 0);
INSERT INTO `department` VALUES (10, '测试一部', 7, '2022-04-24 22:48:24', '2022-04-24 23:10:59', 0);
SET FOREIGN_KEY_CHECKS = 1;
//部门实体
public class Department {
private Long id;
private String name;
private Long parentId;
private Date createTime;
private Date updateTime;
private Integer deleteFlag;
private List<Department> childList;
private List<UserInfo> users;
}
//用户实体
public class UserInfo {
private Long id;
private String name;
private Long departmentId;
}
public class DFSTestMain {
public static void main(String[] args) throws SQLException {
List<Department> departmentList = getDepartmentList();
List<UserInfo> userList = getUserList();
Department depTree = buildDepartment(departmentList);
Map<Long, List<UserInfo>> userInfoMap = userList.stream().collect(Collectors.groupingBy(UserInfo::getDepartmentId));
dfsLoadUsers(depTree , userInfoMap);
System.out.println(JSON.toJSONString(depTree));
}
//获取用户列表
private static List<UserInfo> getUserList() throws SQLException {
List<UserInfo> userInfoList = new ArrayList<>();
String userSql = "SELECT * FROM user_info WHERE delete_flag = 0";
Connection con = JdbcUtil.getCon();
Statement userStat = con.createStatement();
ResultSet userRs = userStat.executeQuery(userSql);
while (userRs.next()) {
UserInfo userInfo = new UserInfo();
userInfo.setId(userRs.getLong(1));
userInfo.setName(userRs.getString(2));
userInfo.setDepartmentId(userRs.getLong(3));
userInfoList.add(userInfo);
}
return userInfoList;
}
//获取部门列表
private static List<Department> getDepartmentList() throws SQLException {
List<Department> departmentList = new ArrayList<>();
String depSql = "SELECT * FROM department WHERE delete_flag = 0";
Connection con = JdbcUtil.getCon();
Statement depStat = con.createStatement();
ResultSet depRs = depStat.executeQuery(depSql);
while (depRs.next()) {
Department department = new Department();
department.setId(depRs.getLong(1));
department.setName(depRs.getString(2));
department.setParentId(depRs.getLong(3));
department.setCreateTime(depRs.getDate(4));
department.setUpdateTime(depRs.getDate(5));
department.setDeleteFlag(depRs.getInt(6));
departmentList.add(department);
}
return departmentList;
}
/**
* TODO 建立部门树
*
* @param departmentList
* @return
*/
private static Department buildDepartment(List<Department> departmentList) {
Map<Long, Department> depMapById = departmentList.stream().collect(Collectors.toMap(Department::getId, dep -> dep));
Department topDep = null;
for (Department dep : departmentList) {
Long parentId = dep.getParentId();
//TODO 无父节点为顶节点
if (null == parentId) {
topDep = dep;
continue;
}
//TODO 无父节点为顶节点
Department parentDep = depMapById.get(parentId);
if (null == parentDep) {
topDep = dep;
continue;
}
List<Department> parentDepChildList = parentDep.getChildList();
if (CollectionUtils.isEmpty(parentDepChildList)) {
parentDep.setChildList(Lists.newArrayList(dep));
} else {
parentDepChildList.add(dep);
}
}
return topDep;
}
//深度优先遍历将员工挂载到每个部门节点
private static void dfsLoadUsers(Department tree, Map<Long, List<UserInfo>> deptIdUsersMap) {
if (tree == null) {
return;
}
List<Department> childList = tree.getChildList();
if (CollectionUtils.isEmpty(childList)) {
tree.setUsers(new ArrayList<>());
return;
}
List<UserInfo> userInfos = deptIdUsersMap.get(tree.getId());
if (!CollectionUtils.isEmpty(userInfos)) {
tree.setUsers(userInfos);
} else {
tree.setUsers(new ArrayList<>());
}
for (Department next : childList) {
dfsLoadUsers(next, deptIdUsersMap);
}
}
}
{
"childList":[
{
"childList":[
{
"childList":[
{
"createTime":"2022-04-23",
"deleteFlag":0,
"id":8,
"name":"大数据研发一部",
"parentId":4,
"updateTime":"2022-04-24",
"users":[
]
},
{
"createTime":"2022-04-24",
"deleteFlag":0,
"id":9,
"name":"大数据开发一部",
"parentId":4,
"updateTime":"2022-04-24",
"users":[
]
}
],
"createTime":"2022-04-23",
"deleteFlag":0,
"id":4,
"name":"大数据部",
"parentId":2,
"updateTime":"2022-04-23",
"users":[
{
"departmentId":4,
"id":0,
"name":"Jerry"
},
{
"departmentId":4,
"id":5,
"name":"Billie"
}
]
},
{
"createTime":"2022-04-23",
"deleteFlag":0,
"id":5,
"name":"商品部",
"parentId":2,
"updateTime":"2022-04-23",
"users":[
]
},
{
"childList":[
{
"createTime":"2022-04-24",
"deleteFlag":0,
"id":10,
"name":"测试一部",
"parentId":7,
"updateTime":"2022-04-24",
"users":[
]
}
],
"createTime":"2022-04-23",
"deleteFlag":0,
"id":7,
"name":"测试部",
"parentId":2,
"updateTime":"2022-04-23",
"users":[
]
}
],
"createTime":"2022-04-23",
"deleteFlag":0,
"id":2,
"name":"技术部",
"parentId":1,
"updateTime":"2022-04-23",
"users":[
{
"departmentId":2,
"id":3,
"name":"Tom"
}
]
},
{
"createTime":"2022-04-23",
"deleteFlag":0,
"id":3,
"name":"财务部",
"parentId":1,
"updateTime":"2022-04-23",
"users":[
]
},
{
"createTime":"2022-04-23",
"deleteFlag":0,
"id":6,
"name":"工程部",
"parentId":1,
"updateTime":"2022-04-23",
"users":[
]
}
],
"createTime":"2022-04-23",
"deleteFlag":0,
"id":1,
"name":"我的公司",
"parentId":0,
"updateTime":"2022-04-23",
"users":[
{
"departmentId":1,
"id":1,
"name":"Jone"
},
{
"departmentId":1,
"id":2,
"name":"Jack"
}
]
}
广度优先遍历全称叫Breath First Search(简称BFS)
,例如查询一棵树从相邻节点进行探索,附近节点探索完毕之后会深入其他层进行查找,由近到远,需要记录之前走过的上一节点方便返回(回溯)
,继续探索其他节点直到结束,如图所示:
最终遍历顺序:A->B->C->D->E->F-G
继续沿用上面项目的组织架构,这里采用广度优先遍历渲染整颗部门树。
public class BFSTestMain {
public static void main(String[] args) throws SQLException {
List<Department> departmentList = getDepartmentList();
List<UserInfo> userList = getUserList();
Map<Long, List<Department>> departmentMap = departmentList.stream().collect(Collectors.groupingBy(Department::getParentId));
Map<Long, List<UserInfo>> userInfoMap = userList.stream().collect(Collectors.groupingBy(UserInfo::getDepartmentId));
LinkedList<Department> queue = new LinkedList<>();
//根节点
Department root = departmentMap.get(0L).get(0);
queue.add(root);
while (!queue.isEmpty()){
Department node = queue.remove();
Long id = node.getId();
//获取子节点
List<Department> childrenList = departmentMap.get(id);
node.setChildList(childrenList);
if (!CollectionUtils.isEmpty(childrenList)){
queue.addAll(childrenList);
}
}
dfsLoadUsers(root , userInfoMap);
System.out.println(JSON.toJSONString(root));
}
private static List<UserInfo> getUserList() throws SQLException {
List<UserInfo> userInfoList = new ArrayList<>();
String userSql = "SELECT * FROM user_info WHERE delete_flag = 0";
Connection con = JdbcUtil.getCon();
Statement userStat = con.createStatement();
ResultSet userRs = userStat.executeQuery(userSql);
while (userRs.next()) {
UserInfo userInfo = new UserInfo();
userInfo.setId(userRs.getLong(1));
userInfo.setName(userRs.getString(2));
userInfo.setDepartmentId(userRs.getLong(3));
userInfoList.add(userInfo);
}
return userInfoList;
}
private static List<Department> getDepartmentList() throws SQLException {
List<Department> departmentList = new ArrayList<>();
String depSql = "SELECT * FROM department WHERE delete_flag = 0 ";
Connection con = JdbcUtil.getCon();
Statement depStat = con.createStatement();
ResultSet depRs = depStat.executeQuery(depSql);
while (depRs.next()) {
Department department = new Department();
department.setId(depRs.getLong(1));
department.setName(depRs.getString(2));
department.setParentId(depRs.getLong(3));
department.setCreateTime(depRs.getDate(4));
department.setUpdateTime(depRs.getDate(5));
department.setDeleteFlag(depRs.getInt(6));
departmentList.add(department);
}
return departmentList;
}
private static void dfsLoadUsers(Department tree, Map<Long, List<UserInfo>> deptIdUsersMap) {
if (tree == null) {
return;
}
List<Department> childList = tree.getChildList();
if (CollectionUtils.isEmpty(childList)) {
tree.setUsers(new ArrayList<>());
return;
}
List<UserInfo> userInfos = deptIdUsersMap.get(tree.getId());
if (!CollectionUtils.isEmpty(userInfos)) {
tree.setUsers(userInfos);
} else {
tree.setUsers(new ArrayList<>());
}
for (Department next : childList) {
dfsLoadUsers(next, deptIdUsersMap);
}
}
}
{
"childList":[
{
"childList":[
{
"childList":[
{
"createTime":"2022-04-23",
"deleteFlag":0,
"id":8,
"name":"大数据研发一部",
"parentId":4,
"updateTime":"2022-04-24",
"users":[
]
},
{
"createTime":"2022-04-24",
"deleteFlag":0,
"id":9,
"name":"大数据开发一部",
"parentId":4,
"updateTime":"2022-04-24",
"users":[
]
}
],
"createTime":"2022-04-23",
"deleteFlag":0,
"id":4,
"name":"大数据部",
"parentId":2,
"updateTime":"2022-04-23",
"users":[
{
"departmentId":4,
"id":0,
"name":"Jerry"
},
{
"departmentId":4,
"id":5,
"name":"Billie"
}
]
},
{
"createTime":"2022-04-23",
"deleteFlag":0,
"id":5,
"name":"商品部",
"parentId":2,
"updateTime":"2022-04-23",
"users":[
]
},
{
"childList":[
{
"createTime":"2022-04-24",
"deleteFlag":0,
"id":10,
"name":"测试一部",
"parentId":7,
"updateTime":"2022-04-24",
"users":[
]
}
],
"createTime":"2022-04-23",
"deleteFlag":0,
"id":7,
"name":"测试部",
"parentId":2,
"updateTime":"2022-04-23",
"users":[
]
}
],
"createTime":"2022-04-23",
"deleteFlag":0,
"id":2,
"name":"技术部",
"parentId":1,
"updateTime":"2022-04-23",
"users":[
{
"departmentId":2,
"id":3,
"name":"Tom"
}
]
},
{
"createTime":"2022-04-23",
"deleteFlag":0,
"id":3,
"name":"财务部",
"parentId":1,
"updateTime":"2022-04-23",
"users":[
]
},
{
"createTime":"2022-04-23",
"deleteFlag":0,
"id":6,
"name":"工程部",
"parentId":1,
"updateTime":"2022-04-23",
"users":[
]
}
],
"createTime":"2022-04-23",
"deleteFlag":0,
"id":1,
"name":"我的公司",
"parentId":0,
"updateTime":"2022-04-23",
"users":[
{
"departmentId":1,
"id":1,
"name":"Jone"
},
{
"departmentId":1,
"id":2,
"name":"Jack"
}
]
}