一. 问题背景
在公司做项目遇到一个需求是要返回一颗树状菜单栏的数据结构给前端。效果类似如下:
二. 解决方案
这里先直接给出核心算法的代码,有兴趣研究的可以前往gitee页面下载,搭建该项目的所有资源(包括源码、sql表结构、表数据都在项目里面)都在里面,部署方法可参考里面的readme.md文件
2.1 核心算法
这里有2种方式,其实都差不多。一种是采用 动态数组 ,一种是采用 队列层次遍历 。直接给出核心代码,后面再给出思路图
2.1.1 用动态数组构建树
/**
* 1. 构建一个map,存储每个节点的孩子们
* 2. 构建一个map,根据key可以获取到这个节点
* 3. 根据传进来的字典定义名获取那个节点,用动态数组构建树
*/
long startTime = System.currentTimeMillis();
if (parentKey == null || parentKey.trim().length() == 0) {
parentKey = PARENT_KEY;
}
// 构建一个map,存储每个节点的孩子节点
Map<Integer, List<DictDefine>> parentIdChildrenMap = new HashMap<>();
// 构建一个map,根据key可以获取到这个节点
Map<String, DictDefine> defineNameDictDefineMap = new HashMap<>();
for (DictDefine dictDefine : dictDefines) {
Integer parentId = dictDefine.getParentId();
List<DictDefine> dictDefineList = parentIdChildrenMap.get(parentId);
if (dictDefineList == null) {
dictDefineList = new ArrayList<>();
}
dictDefineList.add(dictDefine);
parentIdChildrenMap.put(parentId, dictDefineList);
defineNameDictDefineMap.put(dictDefine.getDefineName(), dictDefine);
}
// 根据传进来的字典定义名获取那个节点,用动态数组构建树
DictDefine rootDictDefine = defineNameDictDefineMap.get(parentKey);
if (rootDictDefine == null) {
return new ArrayList<DictDefine>();
}
//用动态数组list构建树
List<DictDefine> list = new ArrayList<>();
list.add(rootDictDefine);
int index = 0;
while (!list.isEmpty() && index < list.size()) {
DictDefine dictDefine = list.get(index);
if (dictDefine == null) {
continue;
}
List<DictDefine> childrenList = parentIdChildrenMap.get(dictDefine.getId());
if (childrenList != null) {
dictDefine.setChildren(childrenList);
list.addAll(childrenList);
}
index++;
}
ArrayList<DictDefine> result = new ArrayList<>();
result.add(rootDictDefine);
long endTime = System.currentTimeMillis();
log.info("It takes {}ms to deal it.", endTime-startTime);
return result;
}
2.1.2 用队列构建树
/**
* 使用队列构建树
* @param dictDefines
* @param parentKey
* @return
*/
public static List<DictDefine> buildTreeByLinkedList(List<DictDefine> dictDefines,
String parentKey) {
/**
* 1. 构建一个map,存储每个节点的孩子们
* 2. 构建一个map,根据key可以获取到这个节点
* 3. 根据传进来的字典定义名获取那个节点,用队列构建树
*/
long startTime = System.currentTimeMillis();
if (parentKey == null || parentKey.trim().length() == 0) {
parentKey = PARENT_KEY;
}
// 构建一个map,存储每个节点的孩子节点
Map<Integer, List<DictDefine>> parentIdChildrenMap = new HashMap<>();
// 构建一个map,根据key可以获取到这个节点
Map<String, DictDefine> defineNameDictDefineMap = new HashMap<>();
for (DictDefine dictDefine : dictDefines) {
Integer parentId = dictDefine.getParentId();
List<DictDefine> dictDefineList = parentIdChildrenMap.get(parentId);
if (dictDefineList == null) {
dictDefineList = new ArrayList<>();
}
dictDefineList.add(dictDefine);
parentIdChildrenMap.put(parentId, dictDefineList);
defineNameDictDefineMap.put(dictDefine.getDefineName(), dictDefine);
}
// 根据传进来的字典定义名获取那个节点,用动态数组构建树
DictDefine rootDictDefine = defineNameDictDefineMap.get(parentKey);
if (rootDictDefine == null) {
return new ArrayList<DictDefine>();
}
//用动态数组list构建树
Queue<DictDefine> queue = new LinkedList<>();
queue.offer(rootDictDefine);
while (!queue.isEmpty() ) {
DictDefine dictDefine = queue.poll();
if (dictDefine == null) {
continue;
}
List<DictDefine> childrenList = parentIdChildrenMap.get(dictDefine.getId());
if (childrenList != null) {
dictDefine.setChildren(childrenList);
queue.addAll(childrenList);
}
}
ArrayList<DictDefine> result = new ArrayList<>();
result.add(rootDictDefine);
long endTime = System.currentTimeMillis();
log.info("It takes {}ms to deal it.", endTime-startTime);
return result;
}
2.2 核心思路
核心思路如上,不管是采用动态数组,还是队列,其实都是在进行层次遍历,有兴趣可以去看看BFS层次遍历算法