JAVA将List转成Tree树形结构数据和深度优先遍历

news2024/10/6 10:37:02

引言:

在日常开发中,我们经常会遇到需要将数据库中返回的数据转成树形结构的数据返回,或者需要对转为树结构后的数据绑定层级关系再返回,比如需要统计当前节点下有多少个节点等,因此我们需要封装一个ListToTree的工具类和学会如何通过深度优先遍历数据。

数据准备:

先简单准备一下具有父子关系的数据。

package data;


/**
* @author sinder
* @date 2023/11/8 21:40
*/
public class OrgData {
    private String id;

    private String pId;

    private String orgName;

    public String getId() {
        return id;
    }

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

    public String getpId() {
        return pId;
    }

    public void setpId(String pId) {
        this.pId = pId;
    }

    public String getOrgName() {
        return orgName;
    }

    public void setOrgName(String orgName) {
        this.orgName = orgName;
    }
}
package data;

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

/**
 * 生成数据,模拟数据库查询
 * @author sinder
 * @date 2023/11/8 22:17
 */
public class SingData {
    public static List<OrgData> getData() {
        OrgData orgData1 = new OrgData();
        orgData1.setId("1");
        orgData1.setpId("root");
        orgData1.setOrgName("根节点A");

        OrgData orgData2 = new OrgData();
        orgData2.setId("2");
        orgData2.setpId("root");
        orgData2.setOrgName("根节点B");

        OrgData orgData3 = new OrgData();
        orgData3.setId("3");
        orgData3.setpId("1");
        orgData3.setOrgName("A目录");

        OrgData orgData4 = new OrgData();
        orgData4.setId("4");
        orgData4.setpId("2");
        orgData4.setOrgName("B目录");

        List<OrgData> list = new ArrayList<>();
        list.add(orgData1);
        list.add(orgData2);
        list.add(orgData3);
        list.add(orgData4);

        return list;
    }
}

正常情况下,我们都会选择封装一个将List转换为Tree的工具类,并且通过链式顺序LinkedList存储来遍历Tree数据,这里使用steam来实现,如下:

package data;

import java.util.List;

/**
 * 构建tree数据
 * @author sinder
 * @date 2023/11/8 22:30
 */
public class TreeBean {
    private String treeId;

    private String treePid;

    private String orgName;

    private Integer flag = 0;

    private List<TreeBean> children;

    private OrgData orgData;

    public String getTreeId() {
        return treeId;
    }

    public void setTreeId(String treeId) {
        this.treeId = treeId;
    }

    public String getOrgName() {
        return orgName;
    }

    public void setOrgName(String orgName) {
        this.orgName = orgName;
    }

    public String getTreePid() {
        return treePid;
    }

    public void setTreePid(String treePid) {
        this.treePid = treePid;
    }

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

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

    public OrgData getOrgData() {
        return orgData;
    }

    public void setOrgData(OrgData orgData) {
        this.orgData = orgData;
    }

    public Integer getFlag() {
        return flag;
    }

    public void setFlag(Integer flag) {
        this.flag = flag;
    }

    @Override
    public String toString() {
        return "TreeBean{" +
                "treeId='" + treeId + '\'' +
                ", treePid='" + treePid + '\'' +
                ", orgName='" + orgName + '\'' +
                ", children=" + children +
                '}';
    }
}
package utils;

import data.OrgData;
import data.SingData;
import data.TreeBean;

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

/**
 * 将List转为Tree
 *
 * @author sinder
 * @date 2023/11/8 22:28
 */
public class ListToTreeUtil {
    public static List<TreeBean> toTree(List<OrgData> list, String root) {
        if (list.isEmpty()) {
            return new ArrayList<>();
        }
        // 构建数据
        List<TreeBean> treeBeans = list.stream().map(item -> {
            TreeBean treeBean = new TreeBean();
            treeBean.setTreeId(item.getId());
            treeBean.setTreePid(item.getpId());
            treeBean.setOrgName(item.getOrgName());
            return treeBean;
        }).collect(Collectors.toList());

        // 构建Map数据
        Map<String, List<TreeBean>> treeMap = treeBeans.stream().collect(Collectors.groupingBy(item -> {
            if (item.getTreePid().isEmpty()) {
                item.setTreePid(root);
            }
            return item.getTreePid();
        }));

        // 将数据进行分组
        return treeBeans.stream().peek(data -> {
            List<TreeBean> children = treeMap.get(data.getTreeId());
            if (children != null && !children.isEmpty()) {
                data.setChildren(children);
            }
        }).filter(data -> data.getTreePid().equals(root)).collect(Collectors.toList());
    }
}

执行结果:

对tree数据进行遍历,通过链表的形式:

import data.OrgData;
import data.SingData;
import data.TreeBean;
import utils.ListToTreeUtil;

import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author sinder
 * @date 2023/11/8 20:44
 */
public class Main {
    public static void main(String[] args) {
        // 模拟查询数据
        List<OrgData> orgDataList = SingData.getData();
        // 构建tree数据
        List<TreeBean> treeBean = ListToTreeUtil.toTree(orgDataList, "root");
        // 使用LinkedList实现链式队列,实现深度遍历
        LinkedList<TreeBean> stack = new LinkedList<>();
        stack.addAll(treeBean);
        while (!stack.isEmpty()) {
            // 从栈顶开始访问
            TreeBean pop = stack.peek();
            // Flag=1表示已经遍历过一次且该节点存在子节点
            if (pop.getFlag() == 1) {
                OrgData orgData =pop.getOrgData();
                List<TreeBean> children = pop.getChildren();
                // 获取子节点的节点名称,也可以进行其他的操作
                List<String> collect = children.stream().map(TreeBean::getOrgName).collect(Collectors.toList());
                StringBuilder builder = new StringBuilder();
                for (String s : collect) {
                    builder.append(s);
                    builder.append(">");
                }
                pop.setOrgName(builder.toString());
                orgData.setOrgName(pop.getOrgName());
                // pop出栈,当前节点已经统计完,出栈获取下一个栈顶peek
                stack.pop();
            } else {
                // flag为0表示未遍历,判断是否已经遍历到叶子节点(最底部)
                if (pop.getChildren()!= null && !pop.getChildren().isEmpty()) {
                    // 非叶子节点
                    pop.setFlag(1);
                    List<TreeBean> children = pop.getChildren();
                    for (TreeBean child : children) {
                        // 将叶子节点入栈,放到栈顶,实现深度遍历,next
                        stack.push(child);
                    }
                } else {
                    // 叶子节点直接出栈即可,cnt为本身
                    stack.pop();
                }
            }
        }

        // 遍历最终的数据
        for (OrgData orgData : orgDataList) {
            System.out.println(orgData.toString());
        }
    }
}

但是现在有一个问题,当我们响应的数据从OrgData换到其他类型的时候,这时候就需要封装成一个泛型类使得我们的tree数据生成类变成一个通用的,完整代码如下:

完整代码:JAVA将List转为Tree和深度优先遍历

package data;

import java.util.List;

/**
 * 定义一个接口,使得每一个响应的数据实体来实现
 * @author sinder
 * @date 2023/11/9 0:31
 */
public interface Tree<T> {
    String getTreeId();

    String getTreePid();

    void setChild(List<T> list);
}
package data;


import java.util.List;

/**
 * 实现Tree<OrgData>并定义响应类型为本身
 * 定义转为树的参数返回
* @author sinder
* @date 2023/11/8 21:40
*/
public class OrgData implements Tree<OrgData>{
    private String id;

    private String pId;

    private String orgName;

    // 转成树需要的参数
    private Integer flag = 0;

    private List<OrgData> child;

    public String getId() {
        return id;
    }

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

    public String getpId() {
        return pId;
    }

    public void setpId(String pId) {
        this.pId = pId;
    }

    public String getOrgName() {
        return orgName;
    }

    public void setOrgName(String orgName) {
        this.orgName = orgName;
    }

    public Integer getFlag() {
        return flag;
    }

    public void setFlag(Integer flag) {
        this.flag = flag;
    }

    public List<OrgData> getChild() {
        return child;
    }

    @Override
    public String toString() {
        return "OrgData{" +
                "id='" + id + '\'' +
                ", pId='" + pId + '\'' +
                ", orgName='" + orgName + '\'' +
                '}';
    }

    @Override
    public String getTreeId() {
        return id;
    }

    @Override
    public String getTreePid() {
        return pId;
    }

    @Override
    public void setChild(List<OrgData> list) {
        this.child = list;
    }
}

ListToTree方法

package utils;

import data.OrgData;
import data.SingData;
import data.Tree;
import data.TreeBean;

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

/**
 * 将List转为Tree
 * <T extends Tree>告诉编译器有Tree的get方法
 *
 * @author sinder
 * @date 2023/11/8 22:28
 */
public class ListToTreeUtil {
    public static <T extends Tree> List<T> toTree(List<T> list, String root) {
        // 当根节点为null时,定义一个初始值,防止null
        String treeRoot = "treeRoot";
        if (root == null) {
            root = treeRoot;
        }
        if (list.isEmpty()) {
            return new ArrayList<>();
        }

        // 构建Map数据
        // 根据pid分组,获取所有的子节点集合
        Map<String, List<T>> childMap =
                list.stream()
                        .collect(Collectors.groupingBy(item -> {
                            String treePid = item.getTreePid();
                            if (treePid == null) {
                                treePid = treeRoot;
                            }
                            return treePid;
                        }));

        String finalRoot = root;
        return list.stream().peek(
                data -> {
                    List<T> children = childMap.get(data.getTreeId());
                    if (children != null && !children.isEmpty()) {
                        data.setChild(children);
                    }
                })
                .filter(data -> data.getTreePid().equals(finalRoot))
                .collect(Collectors.toList());
    }
}

深度优先遍历

import data.OrgData;
import data.SingData;
import data.TreeBean;
import utils.ListToTreeUtil;

import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author sinder
 * @date 2023/11/8 20:44
 */
public class Main {
    public static void main(String[] args) {
        // 模拟查询数据
        List<OrgData> orgDataList = SingData.getData();
        // 构建tree数据
        List<OrgData> treeBean = ListToTreeUtil.toTree(orgDataList, "root");
        // 使用LinkedList实现链式队列,实现深度遍历
        LinkedList<OrgData> stack = new LinkedList<>();
        stack.addAll(treeBean);
        while (!stack.isEmpty()) {
            // 从栈顶开始访问
            OrgData pop = stack.peek();
            // Flag=1表示已经遍历过一次且该节点存在子节点
            if (pop.getFlag() == 1) {
                List<OrgData> children = pop.getChild();
                // 获取子节点的节点名称,也可以进行其他的操作
                List<String> collect = children.stream().map(OrgData::getOrgName).collect(Collectors.toList());
                StringBuilder builder = new StringBuilder();
                for (String s : collect) {
                    builder.append(s);
                    builder.append(">");
                }
                pop.setOrgName(builder.toString());
                // pop出栈,当前节点已经统计完,出栈获取下一个栈顶peek
                stack.pop();
            } else {
                // flag为0表示未遍历,判断是否已经遍历到叶子节点(最底部)
                if (pop.getChild()!= null && !pop.getChild().isEmpty()) {
                    // 非叶子节点
                    pop.setFlag(1);
                    List<OrgData> children = pop.getChild();
                    for (OrgData child : children) {
                        // 将叶子节点入栈,放到栈顶,实现深度遍历,next
                        stack.push(child);
                    }
                } else {
                    // 叶子节点直接出栈即可,cnt为本身
                    stack.pop();
                }
            }
        }

        // 遍历最终的数据
        for (OrgData orgData : orgDataList) {
            System.out.println(orgData.toString());
        }
    }
}

        文章主要讲了tree数据的生成策略和对tree数据的深度遍历,整体上先构建出一个tree数据,为了能兼容多Object数据转为tree之后的遍历,因此使用了泛型,并且定义了Tree接口,提供了getid、getPid、setChild等操作方法,实体类在实现了Tree接口后就可以使用相应的方法来操作父子关系相关的ip和pid以及子节点来构建相关的tree数据,这也方便的在使用LinkedList深度遍历tree能更加灵活的操作父子节点的数据,主要通过出栈入栈来实现深度遍历,一路从父节点一直遍历到叶子节点才停止。

文章仅为参考示例,更多更好的实现方式欢迎留言评论呀!

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

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

相关文章

Redis 键值类型及其存储结构

Redis 键值类型及其存储结构 键值类型 键的数据类型是字符串&#xff0c;值的类型有&#xff1a;字符串、列表、Hash、集合、有序集合。 键的存储和查找 Redis底层键的存储类似于Java中其他Hash存储结构&#xff1a;数组链表的组合。数组中存储的是Key Hash函数对数组长度取模…

《深入理解计算机系统》书籍学习笔记 - 第二课 - 位,字节和整型

Lecture 02 Bits,Bytes, and Integer 位&#xff0c;字节和整型 文章目录 Lecture 02 Bits,Bytes, and Integer 位&#xff0c;字节和整型Byte 字节位操作布尔代数集合的表现形式和操作C语言的逻辑操作 位移操作整型数值范围无符号与有符号数值无符号与有符号在C中 拓展和截断拓…

个人网厅——提取

目录 需求文档 公积金提取类 controller层 service层 service层实现类 1.验证&#xff08;个人账户&#xff09; 2.提交&#xff08;添加&#xff09; controller层 service层 service层实现类 3.分页查询 controller层 service层 service层实现类 4.详情查询 co…

键盘打字盲打练习系列之认识键盘——0

一.欢迎来到我的酒馆 盲打&#xff0c;yyds&#xff01; 目录 一.欢迎来到我的酒馆二.开始练习 二.开始练习 经常看视频&#xff0c;看到别人在键盘上一通干净利索的操作&#xff0c;就打出想要的文字。心里突然来一句&#xff1a;卧槽&#xff0c;打字贼快啊&#xff01;思索下…

【Java笔试强训】Day9(CM72 另类加法、HJ91 走方格的方案数)

CM72 另类加法 链接&#xff1a;另类加法 题目&#xff1a; 给定两个int A和B。编写一个函数返回AB的值&#xff0c;但不得使用或其他算数运算符。 题目分析&#xff1a; 代码实现&#xff1a; package Day9;public class Day9_1 {public int addAB(int A, int B) {// wr…

解析找不到msvcr100.dll文件的解决方法,4个方法修复msvcr100.dll

msvcr100.dll是Microsoft Visual C 2010运行库的组成部分&#xff0c;一些基于Visual C开发的软件运行时会依赖这个dll文件。出现“找不到msvcr100.dll”的错误提示&#xff0c;往往意味着这个文件在你的计算机系统中丢失或损坏&#xff0c;导致相关程序无法正常运行。以下是找…

【Android】画面卡顿优化列表流畅度一

卡顿渲染耗时如图&#xff1a; 卡顿表现有如下几个方面&#xff1a; 网络图片渲染耗时大上下滑动反应慢&#xff0c;甚至画面不动新增一页数据加载渲染时耗时比较大&#xff0c;上下滑动几乎没有反应&#xff0c;画面停止没有交互响应 背景 实际上这套数据加载逻辑已经运行…

数字化广告运营,小迈科技的关键一步

数据驱动广告运营是小迈科技提升整体经营效率、构建竞争优势的重要选择。 截止目前&#xff0c;小迈科技已经完成了数据驱动的广告运营体系的搭建&#xff0c;并通过与神策数据的深入合作&#xff0c;借力神策客户旅程分析平台&#xff0c;在广告投放、运营活动等各个环节实现了…

Samtec连接器技术前沿 | 全新对准功能确保测试和测量应用中的精确对准

【摘要/前言】 Samtec开发了一种创新的易于使用的对准技术&#xff0c;以确保测试和测量应用中的精密、高频压缩安装连接器的峰值性能。下面解释了我们所看到的趋势&#xff0c;并概述了我们针对出现的常见对准挑战所开发的解决方案。 【问题所在】 随着数据传输率的不断提高…

C++的Odyssey之旅——STL

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; &#x1f354;前言&#xff1a;我们已经将基本语法了解的差不多了&#xff0c;现在我们就该进入C中最重要也是最富有特点的一部分——STL。在学习C语言中我们想要使用顺序表、链表等一些数据结构进行做题时都需要进行这…

pyinstaller 错误排查的验证史

在pyinstaller打包时出现如下错误&#xff0c;很明显感觉是路径被转义 或者历史迁移导致的 报错路径&#xff1a;OSError: [WinError 123] 文件名、目录名或卷标语法不正确。: D:\t_job\x07naconda3_20201121\\Lib\\site-packages 安装路径&#xff1a;File "D:\11_job\…

这些机器视觉工程师犯法了,竟然在闲鱼或淘宝上卖公司的机器视觉程序架构源码

目录 ​从个人层面来讲&#xff1a;从公司层面来讲&#xff1a; ​从个人层面来讲&#xff1a; 个人是法盲&#xff0c;法律意识淡薄只是一方面&#xff0c;另外一个方面就是对于代码的所有权&#xff0c;以及代码的安全性重视不够。把机器视觉程序架构源码打包在闲鱼或淘宝上…

软件测试/测试开发丨接口测试Mock实战练习学习笔记

点此获取更多相关资料 本文为霍格沃兹测试开发学社学员学习笔记分享 原文链接&#xff1a;https://ceshiren.com/t/topic/27857 一、Rewrite 1.1、Rewrite 原理 1.2、Rewrite 实战 Tools → Rewrite 勾选 Enable Rewrite 点击下方 Add 按钮新建一个重写的规则 在右侧编辑重…

制作一个模板

创建模拟对象是一种有用的技术&#xff0c;它允许您专注于应用程序的一部分&#xff0c;而不必担心系统中尚未存在的其他部分。我想设计应用程序的主页&#xff0c;我不想因为没有用户系统而分心&#xff0c;所以我只创建了一个用户对象&#xff0c;这样我就可以继续了。 应用…

【紫光同创国产FPGA教程】【PGC1/2KG第七章】7.数字钟实验例程

本原创教程由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处 适用于板卡型号&#xff1a; 紫光同创PGC1/2KG开发平台&#xff08;盘古1K/2K&#xff09; 一&#xff1a;盘古1K/2K开发板&#xff08;紫光同创PGC…

工厂设备扫码使用售卖联网开发需要怎么开发开源代码?

我们将详细介绍如何使用开源代码开发一套用于工厂设备联网统计的系统。我们将详细讨论所需硬件组件的选择、开源框架和库的使用、软件开发流程以及最后的集成和部署。在这个过程中&#xff0c;我们将提供实用的操作步骤和指导&#xff0c;帮助你更容易地完成这个复杂的任务。 …

leaflet:利用Leaflet-Geoman绘制多种图形,导出为geojson文件(135)

第135个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中利用Leaflet-Geoman绘制多种图形,导出为geojson文件。 灵活地配置Leaflet-Geoman的属性,可以产生各种美妙的绘图效果。利用FileSaver可以导出geojson文件。 直接复制下面的 vue+leaflet源代码,操作2分钟…

C语言求解一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?

完整代码&#xff1a; /* 一个整数&#xff0c;它加上100后是一个完全平方数&#xff0c;再加上168又是一个完全平方数&#xff0c;请问 该数是多少&#xff1f;*/ #include<stdio.h>int main(){//num为最终所求那个数int num;//i*i为第一个完全平方数for (int i 10; …

实验5-1——本地yum源的配置

本地yum源的配置 实验步骤&#xff1a; 1.挂载磁盘(可以看这篇博客&#xff1a;磁盘的挂载和卸载&#xff09; &#xff08;1&#xff09;创建挂载点 mkdir /mnt/cdrom &#xff08;2&#xff09;挂载磁盘 mount /dev/sr0 /mnt/cdrom #或者 mount /dev/cdrom /mnt/cdrom …

嵌入式养成计划-47----QT--基于QT的OpenCV库实现人脸识别功能

一百二十一、基于QT的OpenCV库实现人脸识别功能 121.1 UI 界面 登录按钮现在没啥实际作用&#xff0c;因为没加功能&#xff0c;可以添加在识别成功后运行的功能代码 121.2 思路 显示人脸&#xff1a; 通过 VideoCapture 这个类下面的 open() 方法打开摄像头&#xff0c;对…