乐于学习分享… 大家加油努力
package com.tom.backtrack;
import lombok.Data;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 树节点
*
* @author zx
* @date 2025-05-27 19:51
*/
@Data
public class TreeNode {
private Long id;
private Long parentId;
private String name;
private List<TreeNode> children;
/**
* 是否根节点
*/
@Getter
private Boolean rootNode;
/**
* 是否叶子节点
**/
@Getter
private Boolean leafNode;
/**
* 设置子节点数据,设置为protected禁止外部调用
**/
public void setChildren(List<TreeNode> children) {
this.children = children;
this.rootNode = Objects.equals(getParentId(), 0L);
this.leafNode = children == null || children.isEmpty();
}
// 模糊搜索方法,返回符合条件的节点列表
public List<TreeNode> fuzzySearch(String query) {
List<TreeNode> results = new ArrayList<>(); // 存储搜索结果
fuzzySearchHelper(this, query, results); // 调用搜索辅助方法
return results;
}
// 判断节点是否有子节点的方法
private boolean hasLeaf(TreeNode node) {
for (TreeNode child : node.children) {
if (child.children.isEmpty()) { // 如果存在叶子节点,返回true
return true;
}
}
return false; // 否则返回false
}
// 判断节点的子节点是否存在符合条件的节点的方法
private boolean hasMatchingChild(TreeNode node, String query) {
for (TreeNode child : node.children) {
if (child.name.contains(query) || hasMatchingChild(child, query)) {
return true; // 如果子节点的名称包含查询字符串,或者子节点的子节点存在符合条件的节点,则返回true
}
}
return false; // 否则返回false
}
// 递归搜索辅助方法
private void fuzzySearchHelper(TreeNode node, String query, List<TreeNode> results) {
// 如果当前节点的值包含查询字符串,并且至少有一个子节点也符合查询条件,则将其添加到结果列表中
if (node.name.contains(query) || hasMatchingChild(node, query)) {
results.add(node);
}
// 递归搜索子节点
List<TreeNode> removableChildren = new ArrayList<>();
for (TreeNode child : node.children) {
fuzzySearchHelper(child, query, results);
if (!hasLeaf(child)) {
removableChildren.add(child);
}
}
// 删除没有树叶的子节点
for (TreeNode child : removableChildren) {
node.children.remove(child);
}
}
}
package com.tom.backtrack;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* <h1>树工具类</h1>
* <pre>
* 1.该工具类支持: 构建树结构、模糊查询
* 2.优化建议:
* 1.扁平化处理:如果只需要匹配结果而不需要保持树结构,可以先将所有节点展平为列表再进行过滤
* 2.性能优化:
* - 对于大数据量场景,可结合缓存机制或异步加载
* - 使用 equalsIgnoreCase() 支持不区分大小写的模糊匹配
* 3.高级模糊匹配:使用正则表达式或第三方库如 Apache Commons Text 的模糊匹配功能
* 3.注意事项:
* 1.若需返回完整树结构但仅展示匹配路径,可在递归中剪枝保留匹配路径
* 2.若数据来源于数据库,推荐在 SQL 层面使用 LIKE '%keyword%' 进行模糊查询以减少内存开销(多次 SQL 查询 + 应用层拼接)
*
* 案例:
* 场景假设:
* tree_node 的表,用于存储树形结构数据:
* CREATE TABLE tree_node (
* id BIGINT PRIMARY KEY,
* parent_id BIGINT, -- 父节点ID,顶级节点为0或NULL
* name VARCHAR(255) -- 节点名称
* );
* 根据某个关键词模糊查询节点名称,并获取其所有父节点路径或子树结构。
* 实现方式一:多次 SQL 查询 + 应用层拼接(推荐)
* 步骤:
* - 先模糊查询匹配的节点
* - 递归向上查父节点,直到根节点
* - 应用层构建树结构
* 示例代码:
* 1.模糊查询匹配节点
* SELECT * FROM tree_node WHERE name LIKE '%keyword%';
* 2.根据匹配节点 ID,逐级向上查找父节点
* -- 假设匹配到 id = 5 的节点
* SELECT * FROM tree_node WHERE id = 5;
* SELECT * FROM tree_node WHERE id = (SELECT parent_id FROM tree_node WHERE id = 5);
* -- 依次类推,直到 parent_id 为 NULL 或 0
*
* 通过java、python等代码逻辑循环执行上述语句,构建出完整路径。
* 实现方式二:使用存储过程模拟递归查询(适用于固定层级)
* 创建一个存储过程,递归查找所有子节点:
* DELIMITER $$
*
* CREATE PROCEDURE search_tree(IN keyword VARCHAR(255))
* BEGIN
* CREATE TEMPORARY TABLE IF NOT EXISTS temp_result (
* id BIGINT,
* parent_id BIGINT,
* name VARCHAR(255)
* );
*
* TRUNCATE TABLE temp_result;
*
* -- 插入初始匹配节点
* INSERT INTO temp_result (id, parent_id, name)
* SELECT id, parent_id, name FROM tree_node WHERE name LIKE CONCAT('%', keyword, '%');
*
* -- 循环插入父节点
* WHILE ROW_COUNT() > 0 DO
* INSERT INTO temp_result (id, parent_id, name)
* SELECT t.id, t.parent_id, t.name
* FROM tree_node t
* INNER JOIN temp_result tr ON t.id = tr.parent_id
* WHERE t.id NOT IN (SELECT id FROM temp_result);
* END WHILE;
*
* -- 返回结果
* SELECT * FROM temp_result ORDER BY id;
*
* DROP TEMPORARY TABLE IF EXISTS temp_result;
* END$$
*
* DELIMITER ;
*
* 调用方式:
* CALL search_tree('1-2-1');
* 注意:
* - MySQL 5.7 不支持 CTE,建议优先在应用层处理树结构。
* - 如果可以升级到 MySQL 8.0,可以直接使用递归查询(CTE):
* WITH RECURSIVE cte AS (
* SELECT * FROM tree_node WHERE name LIKE '%keyword%'
* UNION ALL
* SELECT t.* FROM tree_node t INNER JOIN cte c ON t.id = c.parent_id
* )
*
* SELECT * FROM cte;
*
* </pre>
* @author zx
* @date 2025-05-27 20:05
*/
public class TreeUtil {
public static List<TreeNode> buildTree(List<TreeNode> treeNodeList) {
if (treeNodeList == null || treeNodeList.size() == 0) {
return treeNodeList;
}
// 2.根据父节点进行分组
Map<Long, List<TreeNode>> groups = treeNodeList.stream().collect(Collectors.groupingBy(TreeNode::getParentId));
return treeNodeList.stream().filter(Objects::nonNull).peek(pnd -> {
List<TreeNode> ts = groups.get(pnd.getId());
pnd.setChildren(ts);
}).filter(TreeNode::getRootNode).collect(Collectors.toList());
}
public static List<TreeNode> fuzzySearch(List<TreeNode> treeNodes, String keyword) {
List<TreeNode> result = new ArrayList<>();
for (TreeNode node : treeNodes) {
traverseAndCollect(node, keyword, result);
}
return result;
}
private static boolean traverseAndCollect(TreeNode node, String keyword, List<TreeNode> result) {
boolean isMatched = false;
//模糊查询---这里使用正则表达式,临时使用字符串包含方法
if (node.getName().contains(keyword)) {
isMatched = true;
}
if (node.getChildren() != null && !node.getChildren().isEmpty()) {
List<TreeNode> matchedChildren = new ArrayList<>();
for (TreeNode child : node.getChildren()) {
if (traverseAndCollect(child, keyword, result)) {
matchedChildren.add(child);
isMatched = true;
}
}
// 可选:只保留匹配的子节点
node.setChildren(matchedChildren);
}
if (isMatched) {
result.add(node);
}
return isMatched;
}
public static List<TreeNode> buildTreeNodeData() {
List<TreeNode> treeNodeList = new ArrayList<>();
TreeNode treeNode1 = new TreeNode();
treeNode1.setId(1L);
treeNode1.setParentId(0L);
treeNode1.setName("1");
treeNodeList.add(treeNode1);
TreeNode treeNode2 = new TreeNode();
treeNode2.setId(2L);
treeNode2.setParentId(0L);
treeNode2.setName("2");
treeNodeList.add(treeNode2);
TreeNode treeNode3 = new TreeNode();
treeNode3.setId(3L);
treeNode3.setParentId(1L);
treeNode3.setName("1-1");
treeNodeList.add(treeNode3);
TreeNode treeNode4 = new TreeNode();
treeNode4.setId(4L);
treeNode4.setParentId(1L);
treeNode4.setName("1-2");
treeNodeList.add(treeNode4);
TreeNode treeNode5 = new TreeNode();
treeNode5.setId(5L);
treeNode5.setParentId(4L);
treeNode5.setName("1-2-1");
treeNodeList.add(treeNode5);
TreeNode treeNode6 = new TreeNode();
treeNode6.setId(6L);
treeNode6.setParentId(2L);
treeNode6.setName("2-1");
treeNodeList.add(treeNode6);
TreeNode treeNode7 = new TreeNode();
treeNode7.setId(7L);
treeNode7.setParentId(2L);
treeNode7.setName("2-2");
treeNodeList.add(treeNode7);
TreeNode treeNode8 = new TreeNode();
treeNode8.setId(8L);
treeNode8.setParentId(6L);
treeNode8.setName("2-2-1");
treeNodeList.add(treeNode8);
return treeNodeList;
}
public static void main(String[] args) {
List<TreeNode> treeNodeList = buildTree(buildTreeNodeData());
List<TreeNode> newTreeNodeList = new ArrayList<>();
newTreeNodeList.addAll(treeNodeList);
System.out.println(newTreeNodeList);
String keyword = "1-2-1";
List<TreeNode> searchResultTreeNodeList = buildTree(fuzzySearch(treeNodeList, keyword));
System.out.println(searchResultTreeNodeList);
}
}