引言
在后端开发中,我们经常需要处理层级数据,比如部门结构、菜单权限或商品分类。数据库通常存储为扁平的列表(List),而前端往往需要嵌套的树形结构(Tree)。
探讨一种基于HashMap的高效算法,实现List到Tree以及Tree到List的双向转换。
场景与模型定义
两种数据结构:
- Node(扁平节点) :包含自身ID和父ID,通常对应数据库表结构。
- Tree(树形节点) :包含自身ID和子节点列表。
// 扁平节点
class Node {
int id;
int pid;
// 构造函数与Getter省略
}
// 树形节点
class Tree {
int id;
List<Tree> children;
// 构造函数与Getter省略
}
核心算法实现
List转Tree
核心思路是利用HashMap的空间换时间策略,将时间复杂度降低到O(n)。
- 第一步:遍历列表,将所有节点存入Map,Key为节点ID。同时,我们需要找到根节点的ID(即最小的pid)。
- 第二步:再次遍历列表,通过Map快速查找父节点,将当前节点加入父节点的children列表。
public static Tree toTree(List<Node> nodes) {
if (nodes == null || nodes.isEmpty()) {
return null;
}
// 1. 初始化Map并寻找根节点ID
var map = new HashMap<Integer, Tree>(nodes.size() + 1);
var rid = Integer.MAX_VALUE;
for (var n : nodes) {
if (n == null) {
continue;
}
// 寻找最小的pid作为根节点ID
if (n.getPid() < rid) {
rid = n.getPid();
}
// 将节点放入Map
map.put(n.getId(), new Tree(n.getId()));
}
if (map.isEmpty()) {
return null;
}
// 确保根节点也在Map中
map.putIfAbsent(rid, new Tree(rid));
var root = map.get(rid);
// 2. 组装树形结构
for (var n : nodes) {
if (n == null) {
continue;
}
var child = map.get(n.getId());
var parent = map.get(n.getPid());
if (parent != null) {
parent.getChildren().add(child);
}
}
return root;
}
Tree转List
这是一个典型的深度优先搜索(DFS)过程,通过递归遍历树的每一层。
public static List<Node> toNodes(Tree tree) {
var data = new ArrayList<Node>();
if (tree == null) {
return data;
}
appendChildren(data, tree);
return data;
}
private static void appendChildren(List<Node> data, Tree tree) {
var children = tree.children;
if (children.isEmpty()) {
return;
}
// 遍历子节点,将其转换为扁平Node并加入列表
for (var c : children) {
data.add(new Node(c.id, tree.id));
// 递归处理下一级
appendChildren(data, c);
}
}
测试与验证
通过一个具体的树形结构来验证代码的正确性。
测试数据:
- 根节点:0
- 一级子节点:1, 2(父节点为0)
- 二级子节点:3, 4(父节点为1)
代码结构:
0
/ \
1 2
/ \
3 4
运行结果:
- 转Tree:
{
"id": 0,
"children": [
{
"id": 1,
"children": [
{
"id": 3,
"children": []
},
{
"id": 4,
"children": []
}
]
},
{
"id": 2,
"children": []
}
]
}
- 转List:
[
{
"id": 1,
"pid": 0
},
{
"id": 3,
"pid": 1
},
{
"id": 4,
"pid": 1
},
{
"id": 2,
"pid": 0
}
]
总结
- 时间复杂度:toTree方法通过两次遍历,时间复杂度为O(n),优于嵌套循环的O(n²)算法。
- 空间复杂度:使用了一个HashMap存储节点引用,空间复杂度为O(n)。
- 适用性:该算法适用于内存中处理中等规模的数据集构建层级关系