Java构建Tree并实现节点名称模糊查询

news2025/6/1 13:45:48

乐于学习分享… 大家加油努力

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);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2392998.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为FreeArc能和其他华为产品共用充电线吗?

最近刚买的FreeArc终于到手啦&#xff0c;看到网上有朋友说&#xff0c;这次的耳机是不附带充电线&#xff0c;开箱后发现果真如此&#xff0c;那FreeArc到底用什么规格的充电线&#xff0c;能不能和华为的Type-C数据线通用&#xff0c;我来给大家解答一下吧&#xff01; Free…

[网页五子棋][匹配模式]创建房间类、房间管理器、验证匹配功能,匹配模式小结

文章目录 创建房间类创建房间类实现房间管理器 实现匹配器(3)验证匹配功能问题&#xff1a;匹配按钮不改变验证多开 小结 创建房间类 LOL&#xff0c;通过匹配的方式&#xff0c;自动给你加入到一个房间&#xff0c;也可手动创建游戏房间 这一局游戏&#xff0c;进行的“场所…

实验设计与分析(第6版,Montgomery)第3章单因子实验:方差分析3.11思考题3.7 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第3章单因子实验&#xff1a;方差分析3.11思考题3.7 R语言解题。主要涉及单因子方差分析&#xff0c;正态性假设检验&#xff0c;残差与拟合值的关系图&#xff0c;平方根变换。 X<-c(…

【知识点】第2章:Python程序实例解析

文章目录 知识点整理Python程序语法元素分析 练习题判断题填空题选择题 知识点整理 Python程序语法元素分析 Python程序包括格式框架、注释、变量、表达式、分支语句、循环语句、函数等语法元素。 程序的格式框架 Python语言采用严格的 “缩进” 来表明程序的格式框架。缩进…

每日Prompt:指尖做画

提示词 微缩景观&#xff0c;微距摄影&#xff0c;俯瞰角度&#xff0c;特写&#xff0c;硕大食指手指甲&#xff0c;一个小小的人正在做画&#xff0c;小人右手拿画笔&#xff0c;小人左手拿调色盘&#xff0c;在指甲上作画&#xff0c;画的是中国古代山水画&#xff0c;背景…

redis未授权(CVE-2022-0543)

概述 Redis 默认绑定在 0.0.0.0:6379&#xff0c;在未配置防火墙或访问控制的情况下会将服务暴露在公网上。若未设置访问密码&#xff08;默认通常为空&#xff09;&#xff0c;攻击者可直接未授权访问 Redis。利用 Redis 提供的 CONFIG 命令&#xff0c;攻击者可修改配置并将…

【运维实战】Linux 中su和sudo之间的区别以及如何配置sudo!

Linux 系统相比其他操作系统具有更高的安全性&#xff0c;其安全机制的核心之一在于用户管理策略和权限控制--普通用户默认无权执行任何系统级操作。 若普通用户需要进行系统级变更&#xff0c;必须通过su或sudo命令提权。 1.su与sudo的本质区别 su 要求直接共享 root 密码&…

浏览器之禁止打开控制台【F12】

前言 在有时我们的日常开发工作中&#xff0c;有些项目要求我们增加禁用控制台的要求&#xff0c;这种虽然很鸡肋&#xff0c;但是它确实存在&#xff0c;并且会让哈哈心里觉得很有成就感。 所以今天他来了。 文章目录 前言无限debugger实现思路&#xff1a;效果如下&#xff1…

GEARS以及与基础模型结合

理解基因扰动的反应是众多生物医学应用的核心。然而&#xff0c;可能的多基因扰动组合数量呈指数级增长&#xff0c;严重限制了实验探究的范围。在此&#xff0c;图增强基因激活与抑制模拟器&#xff08;GEARS&#xff09;&#xff0c;将深度学习与基因-基因关系知识图谱相结合…

计算机网络 | 1.1 计算机网络概述思维导图

附大纲&#xff1a; 计算机网络的概念 一个通过通信设备与线路把不同计算机系统连接起来&#xff0c;实现资源共享和信息传递的系统 计算机网络的组成 从组成成分上 硬件&#xff1a;主机、通信链路、交换设备、通信处理机软件&#xff1a;网络操作系统、聊天软件等协议&…

最悉心的指导教程——阿里云创建ECS实例教程+Vue+Django前后端的服务器部署(通过宝塔面板)

各位看官老爷们&#xff0c;点击关注不迷路哟。你的点赞、收藏&#xff0c;一键三连&#xff0c;是我持续更新的动力哟&#xff01;&#xff01;&#xff01; 阿里云创建ECS实例教程 注意&#xff1a; 阿里云有300元额度的免费适用期哟 白嫖~~~~ 注册了阿里云账户后&#x…

windows中Redis、MySQL 和 Elasticsearch启动并正确监听指定端口

Redis&#xff1a;在 localhost 上启动&#xff0c;并监听端口 6379 MySQL&#xff1a;在 localhost 上启动&#xff0c;并监听端口 3306 Elasticsearch&#xff1a;在 127.0.0.1 上启动&#xff0c;并监听端口 9300 1. Redis 确保 Redis 在 localhost 上启动并监听端口 6379…

学者观察 | Web3.0的技术革新与挑战——北京理工大学教授沈蒙

导语 沈蒙老师认为Web3.0正推动形成新型数据基础设施架构和数据要素流通机制&#xff0c;有望在数字经济时代发挥重要作用&#xff0c;对我国经济发展和社会进步将产生深远影响。AI在推动Web3.0发展方面具有巨大的潜力&#xff0c;但在隐私保护、公平性与安全性等方面也存在“…

pycharm终端遇不显示虚拟环境的问题

大部分我们用pycharm会配合我们的anaconda来使用&#xff0c;但是配置好后&#xff0c;可能会出现pycharm终端不显示虚拟环境的问题。 首先是确定不显示环境&#xff0c;下图中如果没有这个方框&#xff0c;就是不显示虚拟环境。此时用pip或者conda的命令是会提示不是 “不是内…

聊聊网络变压器的浪涌等级标准是怎样划分的呢?

Hqst盈盛&#xff08;华强盛&#xff09;电子导读&#xff1a;聊聊网络变压器的浪涌等级标准是怎样划分的呢&#xff1f; 在和做防雷产品的客户的深度沟通网络变压器产品选型中发现&#xff1a;客户对网络变压器的浪涌等级划分也很希望有更深的了解&#xff0c;今天就这个问题和…

2025年Google I/O大会上,谷歌展示了一系列旨在提升开发效率与Web体验的全新功能

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

ONLYOFFICE文档API:编辑器的品牌定制化

在当今数字化办公时代&#xff0c;文档编辑器已成为各类企业、组织和开发者不可或缺的工具之一。ONLYOFFICE 文档提供的功能丰富且强大的文档编辑 API&#xff0c;让开发者能够根据自己的产品需求和品牌特点&#xff0c;定制编辑器界面&#xff0c;实现品牌化展示&#xff0c;为…

HTTP/HTTPS与SOCKS5三大代理IP协议,如何选择最佳协议?

在复杂多变的网络环境中&#xff0c;代理协议的选择直接影响数据安全、访问效率和业务稳定性。HTTP、HTTPS和SOCKS5作为三大主流代理协议&#xff0c;各自针对不同场景提供独特的解决方案。本文将从协议特性、性能对比到选型策略&#xff0c;为您揭示如何根据业务需求精准匹配最…

远程调用 | OpenFeign+LoadBalanced的使用

目录 RestTemplate 注入 OpenFeign 服务 LoadBalanced 服务 LoadBalanced 注解 RestTemplate 注入 创建 配置类&#xff0c;这里配置后 就不用再重新new一个了&#xff0c;而是直接调用即可 import org.springframework.cloud.client.loadbalancer.LoadBalanced; import …

NSSCTF [NISACTF 2022]ezheap

2058.[NISACTF 2022]ezheap(堆溢出) [NISACTF 2022]ezheap 1.准备 2.ida分析 main函数 int __cdecl main(int argc, const char **argv, const char **envp) {char *command; // [esp8h] [ebp-10h]char *s; // [espCh] [ebp-Ch]setbuf(stdin, 0);setbuf(stdout, 0);s (cha…