当前位置: 首页 > news >正文

java中把一个list转tree的方法

环境

我们有个需求,数据库要存一个无限级联的tree,比如菜单,目录,或者地区等数据,现有两个问题:

  1. 问如何设计表。
  2. 怎么返回给前端一个无线级联的json数据。

思考

第一个问题

在设计表的时候,我们保证每一条数据都有一个code,和parent表示code即可,就可以连成树tree。表设计如下:

CREATE TABLE `city_info`  (`code` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '行政区划代码',`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '名称',`type` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '类型:1-省;2-市;3-县/区',`short_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '简称',`parent` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '所属行政区划',`parents` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '所属行政区划分级'
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '中国省市区县行政区域表' ROW_FORMAT = Dynamic;

第二个问题

我的想法是通过一个sql查询查出来所有数据,得到一个 list集合,然后就回到了主题,如何用java把list转tree。

准备环境

创建一个城市信息类

/*** 中国省市区县行政区域表* @TableName tbl_city_info*/
@TableName(value ="tbl_city_info")
@Data
public class CityInfo implements Serializable {/*** 主键id*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 行政区划代码*/@TableField(value = "code")private String code;/*** 名称*/@TableField(value = "name")private String name;/*** 类型:1-省;2-市;3-县/区*/@TableField(value = "type")private String type;/*** 简称*/@TableField(value = "short_name")private String shortName;/*** 所属行政区划*/@TableField(value = "parent")private String parent;/*** 所属行政区划分级*/@TableField(value = "parents")private String parents;/*** 所属行政区划分级* Mybatis-plus 映射字段时忽略字段*/@TableField(exist = false)private List<CityInfo> childCityInfo;@TableField(exist = false)private static final long serialVersionUID = 1L;
}

创建一个list转成tree的工具类:

public class TreeUtils{public static List<CityInfo> buildTree1(List<CityInfo> cityInfos) {// TODO : 第一种解法return null;}public static List<CityInfo> buildTree2(List<CityInfo> cityInfos) {// TODO : 第二种解法return null;}public static List<CityInfo> buildTree3(List<CityInfo> cityInfos) {// TODO : 第三种解法return null;}
}

第一种方法:递归

/*** 使用递归的方式* @param cityInfos 所有的数据* @return*/public List<CityInfo> buildTree1(List<CityInfo> cityInfos) {List<CityInfo> cityInfoList = new ArrayList<>(16);for (CityInfo cityInfo : cityInfos) {//找到根集合if (cityInfo.getParent().equals("0")) {cityInfoList.add(cityInfo);//添加子setChildren(cityInfos,cityInfo);}}return cityInfoList;}/*** 在所有集合数据中,找到所有的子结果* @param cityInfos 所以的数据* @param parent    需要找的这个城市的所有的子数据*/public void setChildren(List<CityInfo> cityInfos, CityInfo parent) {for (CityInfo cityInfo : cityInfos) {List<CityInfo> childCityInfo = parent.getChildCityInfo();//如果数据和要找的数据code和parent相等,说明找到了if (cityInfo.getParent().equals(parent.getCode())) {//如果是第一次,子结果是null,需要赋值一个空数组if (CollectionUtils.isEmpty(childCityInfo)) {childCityInfo = new ArrayList<>(16);}childCityInfo.add(cityInfo);parent.setChildCityInfo(childCityInfo);}}//如果上面遍历完了,而且子数据还是空,说明没有子数据,直接返回if (CollectionUtils.isEmpty(parent.getChildCityInfo())) {return;}//如果有子数据,需要找子数据,是否有子数据(就是找儿子的儿子)一直递归下去就行了for (CityInfo cityInfo : parent.getChildCityInfo()) {setChildren(cityInfos,cityInfo);}}

第二种方法:两层循环

 /*** 第二种方法:两层循环*      第一层遍历,是为找所有的根节点,*      第二层遍历,是为了找所有节点的,子节点*      *      这里只是返回所有的根节点,因为所有的节点的子节点都找到了,所以只要返回根节点,子节点自己就带上了* @param cityInfos 所有的数据* @return*/public List<CityInfo> buildTree2(List<CityInfo> cityInfos) {List<CityInfo> cityInfoList = new ArrayList<>(16);for (CityInfo cityInfo : cityInfos) {//找到根集合if (cityInfo.getParent().equals("0")) {cityInfoList.add(cityInfo);}//找到本次遍历的节点的,所有子节点(这里子节点就是一层)for (CityInfo child : cityInfos) {if (cityInfo.getCode().equals(child.getParent())) {List<CityInfo> childCityInfo = cityInfo.getChildCityInfo();if (CollectionUtils.isEmpty(childCityInfo)) {childCityInfo = new ArrayList<>(16);cityInfo.setChildCityInfo(childCityInfo);}childCityInfo.add(child);}}}return cityInfoList;}

第三种方法:两次遍历

/*** 第三种方法:两层循环*      第一层遍历,是为了找到所有父code下面的所有的子节点*      第二层遍历,是为了给所有的节点,设置子节点,并且找到所有根节点* @param cityInfos 所有的数据* @return*/public List<CityInfo> buildTree3(List<CityInfo> cityInfos) {Map<String, List<CityInfo>> cityInfoParentMap = new HashMap<>(16);//本次循环,是为了找到所有父code下面的所有的子节点for (CityInfo cityInfo : cityInfos) {String parent = cityInfo.getParent();List<CityInfo> children = cityInfoParentMap.getOrDefault(parent, new ArrayList<>());children.add(cityInfo);cityInfoParentMap.put(parent, children);}List<CityInfo> result = new ArrayList<>(16);//在次循环,是为了给所有的节点,设置子节点
//        for (CityInfo cityInfo : cityInfos) {
//            cityInfo.setChildCityInfo(cityInfoParentMap.get(cityInfo.getCode()));
//        }
//        //最好一次循环,是为了找到所有的根节点
//        for (CityInfo cityInfo : cityInfos) {
//            if (cityInfo.getParent().equals("0")) {
//                result.add(cityInfo);
//            }
//        }//第二次和第三次,可以合成一次循环for (CityInfo cityInfo : cityInfos) {cityInfo.setChildCityInfo(cityInfoParentMap.get(cityInfo.getCode()));if (cityInfo.getParent().equals("0")) {result.add(cityInfo);}}return result;}

第三种方法:两次遍历(使用Java8stream流)

  /*** 第三种方法:两层循环*      第一层遍历,是为了找到所有父code下面的所有的子节点*      第二层遍历,是为了给所有的节点,设置子节点,并且找到所有根节点* @param cityInfos 所有的数据* @return*/public List<CityInfo> buildTree3_stream(List<CityInfo> cityInfos) {//本次循环,是为了找到所有父code下面的所有的子节点Map<String, List<CityInfo>> cityInfoParenMap = cityInfos.stream().collect(Collectors.groupingBy(CityInfo::getParent));//本次循环,是为了给所有的节点,设置子节点cityInfos.forEach(cityInfo -> cityInfo.setChildCityInfo(cityInfoParenMap.get(cityInfo.getCode())));//是为了找到所有的根节点return cityInfos.stream().filter(cityInfo -> cityInfo.getParent().equals("0")).collect(Collectors.toList());}

注意:Collectors.groupingBy()方法使用。查看此链接:https://blog.csdn.net/qq_2662385590/article/details/132385605?spm=1001.2014.3001.5502

三种方法对比

前两种方法的时间复杂度都和叶子节点的个数相关,我们假设叶子节点个数为m

  • 方法一: 用递归的方法,时间复杂度等于:O(n +(n-m)*
    n),根据初始算法那篇文章的计算时间复杂度的方法,可以得到最终时间复杂度是O(n2)
  • 方法二: 用两层嵌套循环的方法,时间复杂度等于:O(n +(n-m)* n),和方法一的时间复杂度是一样的,最终时间复杂度是O(n2)
  • 方法三:用两次遍历的方法,时间复杂度等于:O(3n),根据初始算法那篇文章的计算时间复杂度的方法,可以得到最终时间复杂度是O(n),但它的空间复杂度比前两种方法稍微大了一点,但是也是线性阶的,所以影响不是特别大。
  • 所以第三种方法是个人觉得比较优的一种方法
方法代码执行次数时间复杂度代码复杂程度
方法1O(n +(n-m)* n)平方阶,O(n2)一般
方法2O(n +(n-m)* n)平方阶,O(n2)良好
方法3O(3n)线性阶,O(n)复杂

相关文章:

java中把一个list转tree的方法

环境 我们有个需求&#xff0c;数据库要存一个无限级联的tree&#xff0c;比如菜单&#xff0c;目录&#xff0c;或者地区等数据&#xff0c;现有两个问题&#xff1a; 问如何设计表。怎么返回给前端一个无线级联的json数据。 思考 第一个问题 在设计表的时候&#xff0c;…...

QT设置widget背景图片

首先说方法&#xff0c;在给widget或者frame或者其他任何类型的控件添加背景图时&#xff0c;在样式表中加入如下代码&#xff0c;指定某个控件&#xff0c;设置其背景。 类名 # 控件名 { 填充方式&#xff1a;图片路径 } 例如&#xff1a; QWidget#Widget {border-image: url…...

【ROS】话题通信--从理论介绍到模型实现(C++)

1.简单介绍 话题通信是ROS中使用频率最高的一种通信模式&#xff0c;话题通信是基于发布订阅模式的&#xff0c;也即:一个节点发布消息&#xff0c;另一个节点订阅该消息。像雷达、摄像头、GPS… 等等一些传感器数据的采集&#xff0c;也都是使用了话题通信&#xff0c;换言之…...

服务器数据恢复-EqualLogic存储RAID5数据恢复案例

服务器数据恢复环境&#xff1a; 一台DELL EqualLogic存储中有一组由16块SAS硬盘组建的RAID5阵列。存储存放虚拟机文件&#xff0c;采用VMFS文件系统&#xff0c;划分了4个lun。 服务器故障&检测&分析&#xff1a; 存储设备上有两个硬盘指示灯显示黄色&#xff0c;存储…...

qsort函数详解

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解qsort函数&#xff0c;如果你觉得我写的不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 文章目录 一. qsort函数参数详解1.数组首元素地址base2.数组的元素个数num和元素所占内存空间大小w…...

C#学习,委托,事件,泛型,匿名方法

目录 委托 声明委托 实例化委托 委托的多播 委托的用途 事件 通过事件使用委托 声明事件 泛型 泛型的特性 泛型方法 泛型的委托 匿名方法 编写匿名方法的语法 委托 类似于指针&#xff0c;委托是存有对某个方法的引用的一种引用类型变量&#xff0c;引用可以在运…...

2023最新版本~KEIL5使用C++开发STM32

先看效果 开始教学 因为是第一次写这个配置教程 我会尽量详细些 打开一个Keil工程 移除本地core 添加在线core 第一次编译代码 不会有报错 修改main.c文件类型为C 点击魔术棒 把ARM编译器修改为V6 第二次编译会报错语法不兼容 我把汇编部分的这些代码做了…...

汽车领域专业术语

1. DMS/OMS/RMS/IMS DMS&#xff1a;即Driver Monitoring System&#xff0c;监测对象为Driver&#xff08;驾驶员&#xff09;。DMS三大核心&#xff1a; OMS&#xff1a;即Occupancy Monitoring System&#xff0c;监测对象为乘客。 RMS&#xff1a;后排盲区检测系统 IMS&…...

H3C交换机如何配置本地端口镜像并在PC上使用Wireshake抓包

环境: H3C S6520-26Q-SI version 7.1.070, Release 6326 Win 10 专业版 Wireshake Version 4.0.3 问题描述: H3C交换机如何配置本地端口镜像并在PC上使用Wireshake抓包 解决方案: 配置交换机本地端口镜像 1.进入系统视图,并创建本地镜像组1 <H3C>system-vie…...

零基础自学:2023 年的今天,请谨慎进入网络安全行业

前言 2023 年的今天&#xff0c;慎重进入网安行业吧&#xff0c;目前来说信息安全方向的就业对于学历的容忍度比软件开发要大得多&#xff0c;还有很多高中被挖过来的大佬。 理由很简单&#xff0c;目前来说&#xff0c;信息安全的圈子人少&#xff0c;985、211 院校很多都才…...

向gitee推送代码

目录 一、Gitee创建仓库 二、将刚刚创建的仓库放到虚拟机上 2.1 https 方式克隆仓库 2.2 ssh的方式克隆仓库 三、本地开发&#xff0c;推送 3.1 查看是否有远程库 3.2 推送代码 3.3 查看是否推送成功 一、Gitee创建仓库 二、将刚刚创建的仓库放到虚拟机上 2.1 https 方式…...

双指针算法实例1(移动零)

常⻅的双指针有两种形式&#xff1a; 1 对撞指针&#xff08;左右指针&#xff09;&#xff1a; a 对撞指针从两端向中间移动。⼀个指针从最左端开始&#xff0c;另⼀个从最右端开始&#xff0c;然后逐渐往中间逼 近 b 终止条件一般是两指针相遇or错过&#xff08;也可能在循…...

C#程序随系统启动例子 - 开源研究系列文章

今天讲讲C#中应用程序随系统启动的例子。 我们知道&#xff0c;应用程序随系统启动&#xff0c;都是直接在操作系统注册表中写入程序的启动参数&#xff0c;这样操作系统在启动的时候就根据启动参数来启动应用程序&#xff0c;而我们要做的就是将程序启动参数写入注册表即可。此…...

最全攻略之人工智能顶会论文发表

最全攻略之人工智能顶会论文发表 1. 人工智能顶会1.1 CCF 顶会列表2023年人工智能顶会时间线 2.人工智能顶会论文发表流程2.1 顶会论文发表流程2.2 顶会论文审稿流程 3.1顶会论文发表指南3.1 顶会论文七要素3.2 顶会论文写作要点 4.人工智能发展趋势4.1 人工智能未来趋势4.2 人…...

Redis基于内存的key-value结构化NOSQL(非关系型)数据库

Redis Redis介绍Redis的优点Redis的缺点Redis的安装Redis的连接Redis的使用Redis中的数据类型String的使用get setsetex(expire)ttlsetnx(not exit)HashList列表(队列)Set集合ZSet集合Redis 通用命令Redis图形客户端Redis在Java中的使用RedisTemplate...

Spring学习笔记+SpringMvc+SpringBoot学习笔记

壹、核心概念&#xff1a; 1.1. IOC和DI IOC&#xff08;Inversion of Control&#xff09;控制反转&#xff1a;对象的创建控制权由程序转移到外部&#xff0c;这种思想称为控制反转。/使用对象时&#xff0c;由主动new产生对象转换为由外部提供对象&#xff0c;此过程种对象…...

如何在 3Ds Max 中准确地将参考图像调整为正确的尺寸?

您是否想知道如何在 3Ds Max 中轻松直观地调整参考图像的大小&#xff0c;而无需借助第三方解决方案、插件或脚本&#xff1f; 我问自己这个问题&#xff0c;并高兴地发现了FFD Box 2x2x2&#xff0c;我无法停止钦佩这个修改器的多功能性。 在本文中&#xff0c;我想与您分享一…...

集简云推出的全国第一款 AI+连接器解决方案产品语聚AI

语聚AI是集简云推出的全国第一款 AI连接器解决方案产品&#xff0c;官网&#xff1a;https://yuju.jijyun.cn 语聚AI包括了多个不同的AI功能&#xff0c;协助企业和个人更好的使用AI语言模型所带来的能力&#xff0c;包括&#xff1a; 应用助手 希望通过AI智能助手帮助您查询C…...

git错误记录

露id没有影响&#xff0c;搞得微软不知道我ip一样 git fatal: 拒绝合并无关的历史的错误解决(亲测有效)...

linux使用jmeter进行压测

1.准备好服务器&#xff0c;这里默认服务器用的系统镜像为contos7.9.2009 2.准备好jmeter的测试计划文件 .jmx 这里默认测试计划的jmx文件在 /nas目录下 3.安装JDK与jmeter进行测试 #创建JDK与jmeter目录&#xff0c;并复制安装文件 mkdir /jmeter mkdir /jmeter/jav…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

2024年赣州旅游投资集团社会招聘笔试真

2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

Fabric V2.5 通用溯源系统——增加图片上传与下载功能

fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

前端中slice和splic的区别

1. slice slice 用于从数组中提取一部分元素&#xff0c;返回一个新的数组。 特点&#xff1a; 不修改原数组&#xff1a;slice 不会改变原数组&#xff0c;而是返回一个新的数组。提取数组的部分&#xff1a;slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...

Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?

Pod IP 的本质与特性 Pod IP 的定位 纯端点地址&#xff1a;Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址&#xff08;如 10.244.1.2&#xff09;无特殊名称&#xff1a;在 Kubernetes 中&#xff0c;它通常被称为 “Pod IP” 或 “容器 IP”生命周期&#xff1a;与 Pod …...