java中把一个list转tree的三种方法

说到java把list转tree,网上有一大堆文章,但是我看过之后发现基本都只说了递归和两层嵌套循环两种方法,没人提到两次遍历的方法,我今天就把三种方法都实现以下,做一下对比

问题

周二面试中,面试官提了一个问题,当时答得不是特别好,手写代码能力还是不行啊,一个是比较紧张,一个是代码没法调试,写递归的时候给自己绕晕了。下面是问题:

我们有个需求,数据库要存一个无限级联的tree,比如菜单,或者地区等数据,现有两个问题,1.问如何设计表,2.怎么返回给前端一个无线级联的json数据;

思考

第一个问题,设计表的话,拿地区举例子,这个只要有id,parentId,就没啥问题。主要是第二个问题

create table Zone {
    id varchar(255)
    name varchar(255)
    code varchar(255)
    parentId varchar(255)
}

第二个问题,我的想法是通过一个sql查询查出来所有数据,得到一个 Zone集合,然后就回到了主题,如何用java把list转tree。我第一想法是递归。递归的话,需要考虑几个因素,1.终止条件;2.处理逻辑,3.参数(数据参数,当前层级),4.返回值,然后套入这个问题,分析如下:

  1. 中断条件:当前节点,无子节点,终止退出
  2. 处理逻辑:根据parentId查找节点,设置到parent的children属性中
  3. 参数:数据就是list集合,当前层级参数是parent节点

准备工作

首先我们需要一些辅助类,代码如下

地区类:Zone.class

package com.test;

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

public class Zone {
    private String id;
    private String name;
    private String parentId;
    private List<Zone> children;
    public Zone(String id, String name, String parentId) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
    }
    public void addChildren(Zone zone){
        if(children == null) {
            children = new ArrayList<>();
        }
        children.add(zone);
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getParentId() {
        return parentId;
    }

    public void setParentId(String parentId) {
        this.parentId = parentId;
    }

    public List<Zone> getChildren() {
        return children;
    }

    public void setChildren(List<Zone> children) {
        this.children = children;
    }
}


测试类:Test.java

package com.test;

import cn.hutool.json.JSONUtil;

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

public class Test {
    public static void main(String[] args) {
        List<Zone> zoneList = new ArrayList<>();
        zoneList.add(new Zone("1", "上海", "0"));
        zoneList.add(new Zone("2", "北京", "0"));
        zoneList.add(new Zone("3", "河南", "0"));
        zoneList.add(new Zone("31", "郑州", "3"));
        zoneList.add(new Zone("32", "洛阳", "3"));
        zoneList.add(new Zone("321", "洛龙", "32"));
        zoneList.add(new Zone("11", "松江", "1"));
        zoneList.add(new Zone("111", "泗泾", "11"));
        // 测试第一种方法
        List<Zone> rootZone1 = ZoneUtils.buildTree1(zoneList);
        System.out.println(JSONUtil.toJsonStr(rootZone1));
        // 测试第二种方法
        List<Zone> rootZone2 = ZoneUtils.buildTree2(zoneList);
        System.out.println(JSONUtil.toJsonStr(rootZone2));
        // 测试第三种方法
        List<Zone> rootZone3 = ZoneUtils.buildTree3(zoneList);
        System.out.println(JSONUtil.toJsonStr(rootZone3));
    }
}

地区工具类,提供三种构建tree的方法:ZoneUtils.java

package com.test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

第一种方法:递归

public static List<Zone> buildTree1(List<Zone> zoneList) {
        List<Zone> result = new ArrayList<>();
        for (Zone zone : zoneList) {
            if (zone.getParentId().equals("0")) {
                result.add(zone);
                setChildren(zoneList, zone);
            }
        }
        return result;
    }

    public static void setChildren(List<Zone> list, Zone parent) {
        for (Zone zone : list) {
            if (parent.getId().equals(zone.getParentId())) {
                List<Zone> children = parent.getChildren();
                if (children == null) {
                    children = new ArrayList<>();
                    parent.setChildren(children);
                }
                children.add(zone);
            }
        }
        if (parent.getChildren() == null || parent.getChildren().isEmpty()) {
            return;
        }
        for (Zone zone : parent.getChildren()) {
            setChildren(list, zone);
        }
    }

第二种方法:两层循环

public static List<Zone> buildTree2(List<Zone> zoneList) {
        List<Zone> result = new ArrayList<>();
        for (Zone zone : zoneList) {
            if (zone.getParentId().equals("0")) {
                result.add(zone);
            }
            for (Zone child : zoneList) {
                if (child.getParentId().equals(zone.getId())) {
                    List<Zone> children = zone.getChildren();
                    if (children == null) {
                        children = new ArrayList<>();
                        zone.setChildren(children);
                    }
                    children.add(child);
                }
            }
        }
        return result;
    }

第三种方法:两次遍历

public static List<Zone> buildTree3(List<Zone> zoneList) {
        Map<String, List<Zone>> zoneByParentIdMap = new HashMap<>();
        zoneList.forEach(zone -> {
            List<Zone> children = zoneByParentIdMap.getOrDefault(zone.getParentId(), new ArrayList<>());
            children.add(zone);
            zoneByParentIdMap.put(zone.getParentId(), children);
        });
        zoneList.forEach(zone -> zone.setChildren(zoneByParentIdMap.get(zone.getId())));
        return zoneList.stream()
                .filter(v -> v.getParentId().equals("0"))
                .collect(Collectors.toList());
    }

用java8的stream,三行代码实现。

    public static List<Zone> buildTree3(List<Zone> zoneList) {
        Map<String, List<Zone>> zoneByParentIdMap = zoneList.stream().collect(Collectors.groupingBy(Zone::getParentId));
        zoneList.forEach(zone -> zone.setChildren(zoneByParentIdMap.get(zone.getId())));
        return zoneList.stream().filter(v -> v.getParentId().equals("0")).collect(Collectors.toList());
    }

三种方法对比

前两种方法的时间复杂度都和叶子节点的个数相关,我们假设叶子节点个数为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)复杂
  • 18
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 27
    评论
一个 List 换为树形结构,需要先将 List 的元素按照父子关系进行排序,然后通过递归构建树形结构。下面是一个 Java 实现的示例代码: ```java public class TreeNode { private int id; private String name; private int parentId; private List<TreeNode> children = new ArrayList<>(); // 省略 getter 和 setter 方法 // 将 List 换为树形结构 public static List<TreeNode> buildTree(List<TreeNode> nodeList) { List<TreeNode> treeList = new ArrayList<>(); Map<Integer, TreeNode> nodeMap = new HashMap<>(); // 将节点放入 Map ,方便后续查找 for (TreeNode node : nodeList) { nodeMap.put(node.getId(), node); } // 遍历节点,将子节点添加到父节点的 children 列表 for (TreeNode node : nodeList) { if (node.getParentId() == 0) { // 根节点的 parentId 为 0 treeList.add(node); } else { TreeNode parent = nodeMap.get(node.getParentId()); if (parent != null) { parent.getChildren().add(node); } } } return treeList; } } ``` 示例代码定义了一个 TreeNode 类,表示树节点,包含了节点的 id、name、parentId 和 children 属性。buildTree 方法接收一个 List<TreeNode> 参数,返回树形结构的根节点列表。 在 buildTree 方法,首先遍历 nodeList,将节点放入一个 Map ,方便后续查找。然后再次遍历 nodeList,将子节点添加到父节点的 children 列表。如果当前节点的 parentId 为 0,则说明它是根节点,将其添加到 treeList 返回。最终,treeList 包含了所有根节点,每个根节点下面可能有子节点。
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值