4/11 10:20 更新了一些算法解释
4/18 17:17 更新了生成子树的方法,更加灵活了
先贴代码吧
import java.util.*;
/**
* @Author xjd
* @Date 2019/4/3 10:30
* @Version 1.0
*/
public class GeTreeUtil {
/**
* 传入的节点结构必须继承本工具支持的数据结构
* @author xjd
* @date 2019/4/10 17:20
*/
public static class BaseTreeNode{
protected Long id;
protected Long pid;
protected Boolean leafNode;
protected List childList;
protected BaseTreeNode(Long id, Long pid, Boolean leafNode, List childList) {
this.id = id;
this.pid = pid;
this.leafNode = leafNode;
this.childList = childList;
}
}
/**
* 根据叶子节点生成只包含叶子节点分支的树,可以是深林,最终汇成一棵树
* @param allNodeList 整棵树或者整个深林的所有的节点
* @param leafNodeList 需要展示的叶子节点
* @return 分支树
*/
public static List extends BaseTreeNode> getBranchTree(List extends BaseTreeNode> allNodeList, List extends BaseTreeNode> leafNodeList){
Hashtable> dicNode = new Hashtable<>();
for (BaseTreeNode baseTreeNode : leafNodeList) {
List childList = dicNode.get(baseTreeNode.pid);
if(null == childList){
childList = new ArrayList<>();
dicNode.put(baseTreeNode.pid, childList);
}
childList.add(baseTreeNode);
}
/* Map allNodeMap = new HashMap<>();
for (BaseTreeNode node : allNodeList) {
allNodeMap.put(node.id, node);
}*/
if(dicNode.isEmpty()){
return Collections.emptyList();
}
while (!(dicNode.containsKey(-1L) && dicNode.size() == 1)){ // 递归出口
ConbineDirectParant(allNodeList, dicNode);
// dicNode = ConbineDirectParant(allNodeMap, dicNode); // 这种实现慢一些
}
return dicNode.get(-1L);
}
/**
* 自底向上,每次每个叶子节点上升一层,查找并合并一层的父子关系,通过dicNode里面的父子关系把children并到父节点数据结构的childList中
* @param allNodeList 所有节点,已经找到祖宗孩子关系的节点就remove,不重复添加
* @param dicNode 每次只存一层关系合并完就删除,最后只剩下根节点和第一层节点的父子关系
*/
public static void ConbineDirectParant(List extends BaseTreeNode> allNodeList, Hashtable> dicNode){
Iterator iterator = (Iterator) allNodeList.iterator();
while (iterator.hasNext()){
BaseTreeNode parentNode = iterator.next();
if(dicNode.containsKey(parentNode.id)){ // 爸爸
List childList = dicNode.get(parentNode.id);
// 把父子关系保存在node结构体,dicNode的父子关系就可以remove了
parentNode.childList.addAll(childList);
AddOneNode(parentNode, dicNode);
// 删除已经用完了的下一级,构造dicNode.size==1的递归出口,保证不同树叶子节点都可以递归到深林的根
dicNode.remove(parentNode.id);
iterator.remove();
}
}
}
/**
* 更新dicNode的父子关系,把当前节点合并到上一层的父子关系或者升级到上一层的父子关系
* @param node dicNode中的Key对应的节点,也就是value的父节点
* @param dicNode
*/
private static void AddOneNode(BaseTreeNode node, Hashtable> dicNode) {
List childList = dicNode.get(node.pid);
if(childList != null){
childList.add(node);
}else {
dicNode.put(node.pid, new ArrayList<>(Arrays.asList(node)));
}
}
/**
* 传入一棵树或者深林的需要展示的所有节点,生成深林或者树机构
* @param valueList 需要展示的所有节点
* @return 一棵树或一个深林
*/
public static List> geFullTree(List extends BaseTreeNode> valueList){
Map nodeMap = new HashMap<>();
List rootNodeList = new ArrayList<>();
for (BaseTreeNode node : valueList) {
Long id = node.id;
Long pid = node.pid;
BaseTreeNode baseTreeNode = nodeMap.get(id);
if (baseTreeNode != null) {
node.childList.addAll(baseTreeNode.childList);
// nodeMap.remove(id); // 会自动覆盖相同key,不用remove
}
nodeMap.put(id, node);
BaseTreeNode parentNode = nodeMap.get(pid);
if (parentNode != null) {
if(parentNode.childList == null){
parentNode.childList = new ArrayList<>();
}
parentNode.childList.add(node);
}else{
nodeMap.put(pid, new BaseTreeNode(pid, null, null, new ArrayList<>(Arrays.asList(node))));
}
if(new Long(-1L).equals(pid)){
rootNodeList.add(node);
}
}
return rootNodeList;
}
/**
* 传入所有结点,和需要生成的子树的id
* @param valueList
* @param childTreeRootId
* @return
*/
public static BaseTreeNode GeChildTree(List extends BaseTreeNode> valueList, Long childTreeRootId){
Map nodeMap = new HashMap<>();
for (BaseTreeNode node : valueList) {
Long id = node.id;
Long pid = node.pid;
BaseTreeNode childNode = nodeMap.get(id);
if (childNode == null) {
childNode = node;
}else{
// 遍历到孩子的时候已经put,只拿childlist复制到node
if (childNode.childList != null) {
if (node.childList == null) {
node.childList = new ArrayList<>();
}
node.childList.addAll(childNode.childList);
}
childNode = node;
}
// 每次都put为了nodeMap存的是传进来子类类型
nodeMap.put(id, childNode);
BaseTreeNode parentNode = nodeMap.get(pid);
if (parentNode == null) {
parentNode = new BaseTreeNode(pid, null, false, new ArrayList<>(Arrays.asList(childNode)));
nodeMap.put(pid, parentNode);
}else{
if (parentNode.childList == null) {
parentNode.childList = new ArrayList<>();
}
parentNode.childList.add(childNode);
}
}
return nodeMap.get(childTreeRootId);
}
/**
* 生成分支树的另一种递归实现,直接从dicNode(一代父子关系)查找key对应的节点 而不遍历所有节点
* @param allNodeMap
* @param dicNode
* @return
*/
public static Hashtable> ConbineDirectParant(Map allNodeMap, Hashtable> dicNode){
Hashtable> newdicNode = new Hashtable<>();
for (Long aLong : dicNode.keySet()) {
List childList = dicNode.get(aLong);
TagNode parentNode = allNodeMap.get(aLong);
if (parentNode != null){
if(parentNode.childList == null){
parentNode.childList = new ArrayList<>();
}
for (TagNode childTagNode : childList) {
Integer leafNode = childTagNode.leafNode;
Boolean bLeafNode = new Integer(1).equals(leafNode) ? true : false;
BaseTreeNode baseTreeNode;
if(bLeafNode){
baseTreeNode = new TagNode(childTagNode.tagId, childTagNode.tagPid, bLeafNode, null);
}else{
baseTreeNode = new TagNode(childTagNode.tagId, childTagNode.tagPid, bLeafNode, childTagNode.childList);
}
((TagNode) baseTreeNode).tagName = childTagNode.tagName;
((TagNode) baseTreeNode).tagType = childTagNode.tagType;
((TagNode) baseTreeNode).tagTypeName = childTagNode.tagTypeName;
parentNode.childList.add(baseTreeNode);
}
List tagNodes = dicNode.get(parentNode.tagPid);
if (tagNodes == null) {
tagNodes = new ArrayList<>();
}
tagNodes.add(parentNode);
newdicNode.put(parentNode.tagPid, tagNodes);
allNodeMap.remove(aLong);
}
}
return newdicNode;
}
}
最后一个方法可以不用看,没有完善的,直接传了子类
只要继承我定义的BaseTreeNode结构的节点可以用这个工具
根节点为id为-1
继承BaseTreeNode需要先初始化父类的属性,子类的节点自行构造,但都必须还有父类的属性
生成整棵树的算法应该没什么亮点,把所有节点遍历一次,边存父子关系边合并到根节点。
难点是从叶子节点自底向上生成树
一层一层递归,好像解释都写在代码注释上了。
算法解释:
dicNode是用来存储一代的关系的key--父亲id,value--childlist
先存一份所有叶子结点和其父节点的关系到一个HashTable里(dicNode),这个HashTable只存一代的父子关系,
每次递归 向上查一层父亲,
第一次递归能保存到叶子结点本身(存储在父节点的childList),
叶子结点的pid对应的父节点(存储在dicNode的value中,合并到已经存了伯父的list中或者创建一个暂时只有自身一个元素的list中),
父节点的pid(存储在dicNode的key中),
一共三代关系。
然后remove掉已经归并了的(已经找到组织的)key,为了最后的递归出口是所有关系都归并到一个根节点(id==-1)并且只剩下一代(最顶层的一代)关系,避免了有些叶子结点率先找到了根节点,而有些层次较低的还没找到(如果这时候跳出递归了就会只找到了层次最小的叶子结点的分支,其他的还没来得及合并就被丢弃了),所以要保证dicNode最终有且只有一个根节点关系
第二次以及后面的递归都是向上找一层,合并,最后剩下一个根节点的关系。
有看不懂的评论,我会看。
之前找从叶子节点生成树的方法没找到合适的,就自己尝试着研究一下。
深受张艳玲老师的开源思想的影响,就分享出来了
希望有大佬可以帮我优化一下,感觉我写得还是不够完善。
-----------
引用参考文章 :
https://blog.csdn.net/xuexiaodong009/article/details/12086769