列表与树的处理
最近做一个功能需要将数据库的数据转换成树结构,可以转换成树的数据往往数据库里的表字段含有parent_id这种代表关系,突发奇想,做一个泛型的树工具类,以后遇到这种结构都可以直接转换成树使用。
对象必须具有转换成树的能力
适用于具有父子关系的属性的列表与树的转换,这里封装成了接口TreeAble,提供了找到父节点和自身的索引,其中泛型K是索引的类型。
public interface TreeAble<K> {
K getKey();
K getParentKey();
}
树节点对象
每个树的节点应该是有索引,子节点和树节点的值等属性,完善一点可以包含表示父节点的属性,这里不再冗余,以下结构已经可以表示树的结构了。这里使用了lombok的注解省去了构造器和get,set方法,其中泛型V即是树节点的值。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TreeNode<K, V> {
private K key;
private V value;
private List<TreeNode<K, V>> children;
}
将列表转化成树
准备工作做好后,就可以写工具类了,首先是将数据库里查找出来的列表对象转化成树结构
public static <K, V extends TreeAble<K>> List<TreeNode<K, V>> createTreeByList(List<V> list) {
//用于存放树节点,方便找到
HashMap<K, TreeNode<K, V>> map = new HashMap<>();
//用于存放未找到父节点的对象,此类对象进入下次循环,直到所有对象找到父节点
Set<V> noParent = new HashSet<>();
//虚拟一个最顶层的节点
TreeNode<K, V> top = new TreeNode<>();
//容错处理,循环20次未找到父节点,证明list可能存在环结构,父节点不存在的对象,否则可能出现死循环
int maxNum = 20;
while (list.size() > 0) {
for (V v : list) {
//新建树节点,并存放到map中,方便子节点
TreeNode<K, V> treeNode = new TreeNode<>(v.getKey(), v, null);
map.put(v.getKey(), treeNode);
//父节点为null或者父节点是自己,说明是顶层节点,他的节点就是虚拟顶层节点
TreeNode<K, V> parent = v.getParentKey() == null || v.getParentKey().equals(v.getKey()) ? top : map.get(v.getParentKey());
//父节点如果没有找到,存放到noParent,下次便利是继续
if (parent == null) {
noParent.add(v);
continue;
}
//父节点如果找到了,添加到父节点
if (parent.getChildren() == null) {
parent.setChildren(new ArrayList<>());
}
parent.getChildren().add(treeNode);
}
//重新设置list为没有找到父节点的对象,进入下次循环
list = new ArrayList<>(noParent);
noParent.clear();
//死循环容错处理
maxNum--;
if (maxNum == 0) {
throw new IllegalArgumentException("参数异常,不能组成树结构,请检查是否为(环,父节点不存在,数据过于庞大)");
}
}
//最后返回虚拟顶层节点的子节点既是列表转换后的树结构
return top.getChildren();
}
可以扩展成list转化成单个树
public static <K, V extends TreeAble<K>> TreeNode<K, V> createSingleTreeByList(List<V> list) {
List<TreeNode<K, V>> treeNodes = createTreeByList(list);
if (treeNodes == null || treeNodes.size() != 1) {
throw new IllegalArgumentException("参数异常,不是简单树结构");
}
return treeNodes.get(0);
}
查找一个树下面所有的节点
仅仅转换成树可能还不够,在实际应用中还有很多是需要进一步处理的,比如需要查看一个树下面所有的节点。
public static <K, V extends TreeAble<K>> List<V> getAllChildrenByKey(List<TreeNode<K, V>> tree, K key) {
//采用层级查找的方法,topTree缓存下一次需要查找的层级的所有树节点,知道层级到最后一层
List<TreeNode<K, V>> topTree = new ArrayList<>();
while (tree.size() > 0) {
for (TreeNode<K, V> node : tree) {
//当遍历的树节点的key等于目标key时,找到这个节点下的的所有对象,并返回
if (key.equals(node.getKey())) {
List<V> allNode = getAllNode(node.getChildren());
allNode.add(node.getValue());
return allNode;
} else {
//如果没找到目标key对应的节点,进入下一个层级搜索
if (node.getChildren() != null) {
topTree.addAll(node.getChildren());
}
}
}
tree = new ArrayList<>(topTree);
topTree.clear();
}
return new ArrayList<>();
}
//找到这些节点下的的所有对象
public static <K, V extends TreeAble<K>> List<V> getAllNode(List<TreeNode<K, V>> tree) {
List<V> result = new ArrayList<>();
if (tree != null) {
for (TreeNode<K, V> treeNode : tree) {
//如果有子节点,递归查询这些节点下的的所有对象,最后加上自己本身
if (treeNode.getChildren() != null) {
result.addAll(getAllNode(treeNode.getChildren()));
}
result.add(treeNode.getValue());
}
}
return result;
}
可以扩展成可树化的列表查找下方所有的节点
public static <K, V extends TreeAble<K>> List<V> getAllChildrenByKeyFromList(List<V> tree, K key) {
List<TreeNode<K, V>> treeByList = createTreeByList(tree);
return getAllChildrenByKey(treeByList, key);
}
测试类
可树化的类:
@Data
@AllArgsConstructor
public class TreeDemo implements TreeAble<String> {
private String id;
private String parentId;
@Override
public String getKey() {
return id;
}
@Override
public String getParentKey() {
return parentId;
}
}
Main函数:
public static void main(String[] args) {
List<TreeDemo> treeDemos = Arrays.asList(
new TreeDemo("1", "1"),
new TreeDemo("2", "1"),
new TreeDemo("3", "1"),
new TreeDemo("4", "1"),
new TreeDemo("5", "3"),
new TreeDemo("6", "2"),
new TreeDemo("7", "5"),
new TreeDemo("8", "7"),
new TreeDemo("9", "9"),
new TreeDemo("10", "9"),
new TreeDemo("11", "9"),
new TreeDemo("12", "2"),
new TreeDemo("13", "4"),
new TreeDemo("14", "6"),
new TreeDemo("15", "6"),
new TreeDemo("16", "1")
);
List<TreeNode<String, TreeDemo>> treeNodes = TreeUtil.createTreeByList(treeDemos);
//TreeNode<String, TreeTest> treeNodes = TreeUtil.creteSingleTreeByList(treeTests);
System.out.println(treeNodes);
//List<TreeDemo> allChildrenByKey = TreeUtil.getAllChildrenByKey(treeNodes, "9");
List<TreeDemo> allChildrenByKey = TreeUtil.getAllChildrenByKeyFromList(treeDemos, "1");
System.out.println(allChildrenByKey);
}