java中把一个list转tree的方法

环境

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

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

思考

第一个问题

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

sql 复制代码
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。

准备环境

创建一个城市信息类

java 复制代码
/**
 * 中国省市区县行政区域表
 * @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的工具类:

java 复制代码
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;
    }
}

第一种方法:递归

java 复制代码
/**
     * 使用递归的方式
     * @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);
        }
    }

第二种方法:两层循环

java 复制代码
 /**
     * 第二种方法:两层循环
     *      第一层遍历,是为找所有的根节点,
     *      第二层遍历,是为了找所有节点的,子节点
     *      
     *      这里只是返回所有的根节点,因为所有的节点的子节点都找到了,所以只要返回根节点,子节点自己就带上了
     * @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;
    }

第三种方法:两次遍历

java 复制代码
/**
     * 第三种方法:两层循环
     *      第一层遍历,是为了找到所有父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流)

java 复制代码
  /**
     * 第三种方法:两层循环
     *      第一层遍历,是为了找到所有父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),但它的空间复杂度比前两种方法稍微大了一点,但是也是线性阶的,所以影响不是特别大。
  • 所以第三种方法是个人觉得比较优的一种方法
方法 代码执行次数 时间复杂度 代码复杂程度
方法1 O(n +(n-m)* n) 平方阶,O(n2) 一般
方法2 O(n +(n-m)* n) 平方阶,O(n2) 良好
方法3 O(3n) 线性阶,O(n) 复杂
相关推荐
程序猿麦小七7 分钟前
今天给在家介绍一篇基于jsp的旅游网站设计与实现
java·源码·旅游·景区·酒店
Dontla18 分钟前
Rust泛型系统类型推导原理(Rust类型推导、泛型类型推导、泛型推导)为什么在某些情况必须手动添加泛型特征约束?(泛型trait约束)
开发语言·算法·rust
张某布响丸辣20 分钟前
SQL中的时间类型:深入解析与应用
java·数据库·sql·mysql·oracle
喜欢打篮球的普通人25 分钟前
rust模式和匹配
java·算法·rust
java小吕布38 分钟前
Java中的排序算法:探索与比较
java·后端·算法·排序算法
慢生活的人。44 分钟前
SpringSecurity+jwt+captcha登录认证授权总结
java·认证·rbac·权限·验证
Neophyte06081 小时前
C++算法练习-day40——617.合并二叉树
开发语言·c++·算法
向阳12181 小时前
LeetCode40:组合总和II
java·算法·leetcode
慕容复之巅1 小时前
基于MATLAB的条形码的识别图像处理报告
开发语言·图像处理·matlab
云空1 小时前
《InsCode AI IDE:编程新时代的引领者》
java·javascript·c++·ide·人工智能·python·php