ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

自定义树形结构

2021-05-28 18:00:05  阅读:154  来源: 互联网

标签:node return 自定义 list List param 树形 节点 结构


这里简单说明一下,这里的树是用户自定义的树,由自己控制父子关系。

在很多应用场景下,用户需要自己去维护一颗树,这颗树一般以列表形式存在数据库里面,如下:

在这里插入图片描述
查看树形结构需要由列表生成树,然后再去维护节点关系。
列表生成树的时候,一般的做法是递归查询数据库并组装节点。
这种方法简单,但是一次请求会查询多次数据库,正常来说是不允许这样做的。
为了不多次查询数据库,需要把这个列表一次性查询出来,然后在内存里面把这个列表组装成树,这里有一个问题,如果父节点在前,子节点就直接绑定在父节点,这是没问题的。但是,如果子节点在前,组装的结果就会很实际上的不一样。

直接上代码

TreeMeta

import java.util.List;

/**
 * 需要实现这个接口才能构造树
 *
 * @see com.platform.common.tree.Tree
 * @param <K> 一般是String、int、long
 * @param <T> vlaue,一般是对象
 * @author qiudw
 * @since 1.0.0
 */
public interface TreeMeta<K, T> {

	/**
	 * key作为树里面唯一的标识
	 * @return key
	 */
	K getKey();

	/**
	 * 父key
	 * @return key
	 */
	K getParent();

	/**
	 * 设置子节点
	 * @param children 子节点列表
	 */
	void setChildren(List<T> children);

}

ITree

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * 树接口
 * @param <K> key标识唯一数节点
 * @param <T> key,对应的value
 * @author qiudw
 * @since 1.0.0
 */
public interface ITree<K, T extends TreeMeta<K, T>> {

	/**
	 * 树转换为List, ArrayList
	 * @return 列表
	 */
	List<T> asList();

	/**
	 * 添加节点,使用t.getKey()和getParent()确定关系
	 * @param t 元素
	 */
	void add(T t);

	/**
	 * 列表转树,直接转换需要先添加根节点
	 * @param list 列表
	 */
	default void addAll(List<T> list) {
		list.forEach(this::add);
	}

	/**
	 * 删除节点,包括子节点
	 * @param k 节点key
	 */
	void delete(K k);

	/**
	 * 根据key查抄节点
	 * @param k key
	 * @return value
	 */
	T find(K k);

	/**
	 * 迭代器
	 * @return 返回List的迭代器
	 */
	default Iterator<T> iterator() {
		return asList().iterator();
	}

	/**
	 * 返回所有的key
	 * @return key, 为
	 */
	Set<K> keys();

	/**
	 * 查询父节点,不包括当前节点
	 * @param k key
	 * @return 父节点,到根的一级,根节点或者未找到该节点返回空集合
	 */
	Set<K> findPath(K k);

	/**
	 * 查询所有父节点的key,去重
	 * @param collection 子节点
	 * @return 父节点
	 */
	Set<K> findPath(Collection<K> collection);

	/**
	 * 返回节点数量
	 * @return 数量
	 */
	int size();

	/**
	 * 清空树
	 */
	void empty();

	/**
	 * 是否存在
	 * @param k key
	 * @return 存在返回true
	 */
	boolean exists(K k);

	/**
	 * 生成树,用于多个根节点
	 * @return 结果
	 */
	List<T> asListTree();

}

Tree

import lombok.Data;
import lombok.ToString;

import java.util.*;

/**
 * 自定义树,多个根节点,有点类似TreeMap
 *
 * @param <K> key
 * @param <T> value
 *
 * @author qiudw
 * @since 1.0.0
 */
@ToString
public class Tree<K, T extends TreeMeta<K, T>> implements ITree<K, T> {

	/** 根节点 */
	List<Node<K, T>> rootNodes = new ArrayList<>();
	/** 节点个数 */
	private int size;
	/** 用来存放临时的节点,暂时找不到父节点的那种 */
	private final List<Node<K, T>> tempNodes = new ArrayList<>();
	/** 临时节点集合大小 */
	private int tempSize;

	/**
	 * 无参构造方法
	 */
	public Tree() {}

	/**
	 * 构造方法,解析列表生成树
	 * @param list 列表
	 */
	public Tree(List<T> list) {
		addAll(list);
	}

	/**
	 * 节点
	 * @param <K> key
	 * @param <T> value
	 */
	@Data
	static class Node<K, T> {
		K k;
		T t;
		List<Node<K, T>> children;

		public Node(K k, T t) {
			this.k = k;
			this.t = t;
		}

		Node(K k, T t, List<Node<K, T>> children) {
			this.k = k;
			this.t = t;
			this.children = children;
		}

		/**
		 * 添加子节点,记得设置parent
		 * @param node 节点
		 */
		void addSubNode(Node<K, T> node) {
			if (children == null) {
				children = new ArrayList<>();
			}
			children.add(node);
		}

	}

	@Override
	public List<T> asList() {
		List<T> result = new LinkedList<>();
		// 这里树上的
		recurse(result, rootNodes);
		// 临时节点也需要
		tempNodes.forEach(tempNode -> result.add(tempNode.getT()));
		return result;
	}

	@Override
	public void add(T t) {
		// key
		K key = t.getKey();
		// 查询节点
		Node<K, T> node = findNode(key);
		// 是否能够查询到节点
		if (node == null) {
			// 节点不存在,查询父节点
			K parent = t.getParent();
			// 判断父节点ID是否为空
			if (parent == null) {
				// 父ID为空,直接放在根节点上
				rootNodes.add(new Node<>(key, t));
				// 临时元素大小+1,为了触发刷新节点
				this.tempSize++;
			} else {
				// 放在临时节点
				tempNodes.add(new Node<>(key, t));
			}
			// 刷新节点,解决子节点先添加的情况
			refreshNodes();
			// 条数+1
			this.size++;
		} else {
			// 节点存在,重写赋值
			node.setK(t.getKey());
			node.setT(t);
		}
	}

	@Override
	public void delete(K k) {
		// 先临时节点去删,如果在临时节点上,删除并结束
		if (tempNodes.size() > 0) {
			Iterator<Node<K, T>> iterator = tempNodes.iterator();
			while (iterator.hasNext()) {
				Node<K, T> next = iterator.next();
				if (next.getK().equals(k)) {
					// 临时节点删除
					iterator.remove();
					// 条数-1
					this.tempSize--;
					this.size--;
					return;
				}
			}
		}

		// 节点删除
		delete(rootNodes, k);
		// 重写计算大小
		this.size = 0;
		reloadSize(rootNodes);
		// 需要加上临时集合大小
		this.size += tempNodes.size();
	}

	@Override
	public T find(K k) {
		// 临时节点查找
		for (Node<K, T> tempNode : tempNodes) {
			if (tempNode.getK().equals(k)) {
				return tempNode.getT();
			}
		}
		Node<K, T> node = findNode(k);
		if (node != null) {
			return node.getT();
		}
		return null;
	}

	@Override
	public Set<K> keys() {
		Set<K> set = new HashSet<>();
		getKeys(set, rootNodes);
		// 临时节点
		tempNodes.forEach(tempNode -> set.add(tempNode.getK()));
		return set;
	}

	@Override
	public Set<K> findPath(K k) {
		// 集合保存结果
		Set<K> keys = new LinkedHashSet<>();
		// 当前节点
		Node<K, T> currentNode = findNode(k);
		if (currentNode == null) {
			return keys;
		}
		// 转成列表
		List<T> list = asList();
		// 路径查找
		findPath(keys, list, currentNode.getT());
		return keys;
	}

	@Override
	public Set<K> findPath(Collection<K> collection) {
		Set<K> set = new LinkedHashSet<>();
		collection.forEach(key -> {
			Set<K> path = findPath(key);
			if (path != null) {
				set.addAll(path);
			}
		});
		return set;
	}

	@Override
	public int size() {
		return this.size + tempNodes.size();
	}

	@Override
	public void empty() {
		// 大小归零
		this.size = 0;
		// 根清空
		rootNodes.clear();
		// 临时节点清空
		tempNodes.clear();
	}

	@Override
	public boolean exists(K k) {
		for (Node<K, T> tempNode : tempNodes) {
			if (tempNode.getK().equals(k)) {
				return true;
			}
		}
		Node<K, T> node = findNode(k);
		return node != null;
	}

	@Override
	public List<T> asListTree() {
		List<T> treeList = new ArrayList<>();
		// 生成树
		node2Tree(treeList, rootNodes);
		// 临时节点
		tempNodes.forEach(node -> treeList.add(node.getT()));
		return treeList;
	}

	/**
	 * node转成树
	 * @param list 列表
	 * @param nodeList 节点
	 */
	private void node2Tree(List<T> list, List<Node<K, T>> nodeList) {
		nodeList.forEach(node -> {
			T t = node.getT();
			list.add(t);
			if (node.getChildren() != null && node.getChildren().size() > 0) {
				// 子节点
				List<T> children = new ArrayList<>();
				t.setChildren(children);
				node2Tree(children, node.getChildren());
			}
		});
	}

	/**
	 * 查找节点
	 * @param k key
	 * @return 为查询到返回null
	 */
	private Node<K, T> findNode(K k) {
		return findNode(rootNodes, k);
	}

	/**
	 * 递归查询节点
	 * @param nodeList 节点列表
	 * @param k key
	 * @return 查询到的节点
	 */
	private Node<K, T> findNode(List<Node<K, T>> nodeList, K k) {
		for (Node<K, T> node : nodeList) {
			if (node.getK().equals(k)) {
				return node;
			} else {
				List<Node<K, T>> subNodes = node.getChildren();
				if (subNodes != null) {
					Node<K, T> subNode = findNode(subNodes, k);
					if (subNode != null) {
						return subNode;
					}
				}
			}
		}
		return null;
	}

	/**
	 * 刷新节点,为了解决"子节点"先添加的情况 <br/>
	 * 需要考虑子节点原本就在父节点下 <br/>>
	 * 当前节点不需要与其对应的所有子节点对比 <br/>
	 *
	 */
	private void refreshNodes() {
		while (tempNodes.size() != tempSize) {
			// 临时条数重新赋值
			this.tempSize = tempNodes.size();
			// 迭代器,需要在遍历的时候删除元素
			Iterator<Node<K, T>> iterator = tempNodes.iterator();
			while (iterator.hasNext()) {
				Node<K, T> node = iterator.next();
				// 父节点
				Node<K, T> parentNode = findNode(node.getT().getParent());
				if (parentNode == null) {
					continue;
				}
				parentNode.addSubNode(node);
				iterator.remove();
			}
		}
	}

	/**
	 * 递归生成列表
	 * @param result 集合,所有的元素都放在这里面
	 * @param list 节点列表
	 */
	private void recurse(List<T> result, List<Node<K, T>> list) {
		for (Node<K, T> node : list) {
			// 加入集合
			result.add(node.getT());
			if (node.getChildren() != null) {
				recurse(result, node.getChildren());
			}
		}
	}

	/**
	 * 删除节点
	 * @param list 集合
	 * @param k key
	 */
	private void delete(List<Node<K, T>> list, K k) {
		Iterator<Node<K, T>> iterator = list.iterator();
		while (iterator.hasNext()) {
			Node<K, T> next = iterator.next();
			if (next.getK().equals(k)) {
				iterator.remove();
			} else {
				List<Node<K, T>> children = next.getChildren();
				if (children != null) {
					delete(children, k);
				}
			}
		}
	}

	/**
	 * 重写加载集合大小
	 * @param list 列表
	 */
	private void reloadSize(List<Node<K, T>> list) {
		for (Node<K, T> node : list) {
			this.size++;
			List<Node<K, T>> children = node.getChildren();
			if (children != null) {
				reloadSize(children);
			}
		}
	}

	/**
	 * 生成key集合
	 * @param keys key集合,结果存储在这里面
	 * @param list 节点列表
	 */
	private void getKeys(Set<K> keys, List<Node<K, T>> list) {
		for (Node<K, T> node : list) {
			keys.add(node.getK());
			List<Node<K, T>> children = node.getChildren();
			if (children != null) {
				getKeys(keys, children);
			}
		}
	}

	/**
	 * 查找
	 * @param result parentKeys
	 * @param tempList 待检索的列表
	 * @param t 当前对象
	 */
	public void findPath(Set<K> result, List<T> tempList, T t) {
		K parent = t.getParent();
		if (parent != null) {
			for (T t1 : tempList) {
				if (t1.getKey().equals(parent)) {
					result.add(t1.getKey());
					findPath(result, tempList, t1);
				}
			}
		}
	}

}

测试

实体类TreeItem,这里可以随便添加属性

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 实体类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TreeItem implements TreeMeta<String, TreeItem> {

	private String id;
	private String parent;
	private String name;
	private List<TreeItem> children;

	public TreeItem(String id, String parent, String name) {
		this.id = id;
		this.parent = parent;
		this.name = name;
	}

	@Override
	public String getKey() {
		return id;
	}

	@Override
	public String getParent() {
		return parent;
	}

}

测试

import java.util.ArrayList;
import java.util.List;

/**
 * 树测试
 *
 * @author qiudw
 * @since 1.0.0
 */
public class TreeTest {

	public static void main(String[] args) {
		List<TreeItem> list = new ArrayList<>();
		list.add(new TreeItem("1-1", "1", "二级根节点1-1"));
		list.add(new TreeItem("1-2", "1", "二级根节点1-2"));
		list.add(new TreeItem("1-2-1", "1-2", "三级根节点1-2-1"));
		list.add(new TreeItem("1-2-1", "1-2", "三级根节点1-2-2"));

		list.add(new TreeItem("2-1", "2", "二级根节点2-1"));
		list.add(new TreeItem("2-2", "2", "二级根节点2-2"));
		list.add(new TreeItem("2-2-1", "2-2", "三级根节点2-2-1"));
		list.add(new TreeItem("2-2-1", "2-2", "三级根节点2-2-2"));
		list.add(new TreeItem("1", null, "一级根节点1"));
		list.add(new TreeItem("2", null, "一级根节点2"));

		ITree<String, TreeItem> tree = new Tree<>(list);

		System.out.println(tree);

		List<TreeItem> treeItems = tree.asListTree();
		System.out.println(treeItems);
	}

}

在这里插入图片描述

泛型,为了使用起来方便

  • TreeMeta:获取key、获取父节点、设置子节点列表,加入这颗树里面的元素需要实现这个类
  • ITree:这是接口,也可以不要
  • Tree:这是实现类,也是核心,下面有详细讲解

在这里插入图片描述
Node作为Tree的内部类,存储节点以及父子关系

再看几个属性

  • rootNodes:根节点列表,这颗树有多个根节点
  • size:节点个数
  • tempNodes:临时节点,用来存放那些暂时找不到父节点的元素
  • tempSize:临时节点集合大小

方法

  • List asList():树转换为列表
  • void add(T t):添加节点
  • void delete(K k):删除节点
  • T find(K k):查找节点
  • Set keys():返回所有的key
  • Set findPath(K k):查询父节点,到根为止的集合
  • Set findPath(Collection collection):同上,常用场景,子节点选中的时候,改子节点的父节点也要选中,父节点的父节点需要选中,到根节点为止。
  • int size():节点个数
  • void empty():清空树
  • boolean exists(K k):判断节点是否存在
  • List asListTree():转换为目标树形结构

代码里面注释得比较详细了,自己看看应该能理解的。

标签:node,return,自定义,list,List,param,树形,节点,结构
来源: https://blog.csdn.net/admin_15082037343/article/details/117361461

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有