返回顶部
首页 > 资讯 > 后端开发 > Python >Java 处理树形结构数据的过程
  • 103
分享到

Java 处理树形结构数据的过程

java 树形结构数据java 结构数据 2022-11-13 13:11:14 103人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录前言处理过程接下来分析下具体的实现;1、导入的时候以层级保存数据 2、返回树形层级结构数据返回树形层级数据数据导入前言 问题的背景大概是这样的,有下面这样一个excel

前言

问题的背景大概是这样的,有下面这样一个excel表,原始数据结构如下:

需求点:

  • 导入excel的机构层级数据到Mysql的机构表(这里假设为 depart);
  • 导入的机构数据以层级进行保存和区分;
  • 界面展示时需要以完整的树形层级进行展示;

处理过程

按照上面已知的信息,设计一个简单的机构表

CREATE TABLE `depart` (
  `depart_id` varchar(64) DEFAULT NULL,
  `pid` varchar(64) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `path` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 

接下来分析下具体的实现;

1、导入的时候以层级保存数据 

有做过树形结构处理业务的小伙伴们对树形结构的处理并不陌生,主要就是在机构表中合理运用 id 和 pid 构建数据层级关系,期间可能涉及到递归的操作(不建议在数据库层面递归);

但我们说具体问题具体分析,比如按照上面的exce表的数据结构,以 “/产品研发中心/业务一部” 这个机构为例做简单说明

  • 以 “/” 区分,这条数据涉及到2个部门,“产品研发中心” 为一级部门,而 “业务一部” 为其子部门,即二级部门;
  • 按照第一步的分析,需要将这条数据拆开来做处理,通过部门名称以及其所在层级(业务中可能还存在其他字段),即在查库过程中作为查询条件;
  • 需要考虑excel中的数据存在性,比如 “产品研发中心” 这条数据在数据库中可能存在,也可能不存在,“业务一部” 也可能存在或不存在;
  • 在第三步的基础上,需要按照程序的逻辑设置一定的规则,大概如下:1、顶级部门不存在,可以认为这条数据在数据库中不存在,处理的时候直接按照正常的规则;2、顶级部门存在,直接处理最后一级数据;
  • 不考虑中间部门层级,像 “/A/B/C” 中的B是否存在问题;

2、返回树形层级结构数据

相比导入来说,返回树形层级结构相对来说,已经有相对成熟的处理方式,大致思路如下:

  • 查询所有数据(如果考虑动态加载另说);
  • 构建返回数据的树形结构(可根据 id 和 pid 的关系);

树形结构的返回对象结构大致如下

public class DepartDTO {
    private String departId;
    private String pid;
    private String name;
    private String path;
    private String pname;
    private List<DepartDTO> children;
}

返回树形层级数据

先来做第二个需求,由于解决思路已经给出,只需要按照思路进行即可,下面贴出主要的逻辑代码

    public static String random() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
    public static List<DepartDTO> getDepartTree(List<DepartDTO> allDepart) {
        //查询到的所有的部门数据
        //List<DepartDTO> allDepart = getAllDepart();
        //最高级别用户集合
        List<DepartDTO> roots = new ArrayList<>();
        List<DepartDTO> res = new ArrayList<>();
        for (DepartDTO departDto : allDepart) {
            //-1表示最高级别的用户
            if (departDto.getPid().equals("0")) {
                roots.add(departDto);
            }
        }
        //从最高级别用户开始遍历,递归找到该用户的下级用户,将带有下级的最高级用户放入返回结果中
        for (DepartDTO userDto : roots) {
            userDto = buildUserTree(allDepart, userDto);
            res.add(userDto);
        }
        return res;
    }
    public static DepartDTO buildUserTree(List<DepartDTO> allDeparts, DepartDTO departDTO) {
        List<DepartDTO> children = new ArrayList<>();
        //遍历查到的所有用户
        for (DepartDTO departDTO1 : allDeparts) {
            //-1代表根节点,无需重复比较
            if (departDTO1.getPid().equals("0") || departDTO1.getPname().equals("") || departDTO1.getPname() == null)
                continue;
            //当前用户的上级编号和传入的用户编号相等,表示该用户是传入用户的下级用户
            if (departDTO1.getPname().equals(departDTO.getName())) {
                //递归调用,再去寻找该用户的下级用户
                departDTO1 = buildUserTree(allDeparts, departDTO1);
                //当前用户是该用户的一个下级用户,放入children集合内
                children.add(departDTO1);
            }
        }
        //给该用户的children属性赋值,并返回该用户
        departDTO.setChildren(children);
        return departDTO;
    }

这里先直接模拟一部分的数据,通过这部分的数据做处理

static List<String> departLists = new ArrayList<>();
    static {
        departLists.add("/产品研发中心/业务中台部");
        departLists.add("/产品研发中心/技术中台部");
        departLists.add("/产品研发中心/技术中台部/产品A组");
        departLists.add("/总裁办");
        departLists.add("/总裁办/品牌管理部");
    }
    public static void main(String[] args) {
        List<DepartDTO> allDepart = getAllDepart();
        List<DepartDTO> departTree = getDepartTree(allDepart);
        System.out.println(departTree);
    }

上面程序用到的两个工具

public class PathUtils {
    public static List<String> getPaths(String path) {
        List<String> parentPaths = getParentPaths(path);
        parentPaths.add(path);
        return parentPaths;
    }
    public static String getParentPath(String path) {
        return StringUtils.substringBeforeLast(StringUtils.removeEnd(path, "/"), "/");
    }
    public static List<String> getParentPaths(String path) {
        List<String> paths = new ArrayList<>();
        while (true) {
            path = getParentPath(path);
            if (StringUtils.isBlank(path)) {
                break;
            }
            paths.add(path);
        }
        paths.sort(String::compareTo);
        return paths;
    }
    
    public static String merge(List<String> paths) {
        return merge(paths, null);
    }
    public static String merge(List<String> paths, Function<String, String> function) {
        if (CollectionUtils.isEmpty(paths)) {
            return null;
        }
        Stream<String> stream = paths.stream();
        if (Objects.nonNull(function)) {
            stream = stream.map(function);
        }
        return stream.filter(Objects::nonNull).collect(joining("/", "/", ""));
    }
    public static String getNextPath(String path) {
        path = StringUtils.removeEnd(path, "/");
        String parentPath = StringUtils.substringBeforeLast(path, "/");
        int val = NumberUtils.toInt(StringUtils.substringAfterLast(path, "/")) + 1;
        return parentPath + "/" + StringUtils.leftPad(String.valueOf(val), 4, "0");
    }
    public static String getNextPath(String parentPath, List<String> childPaths) {
        if (CollectionUtils.isEmpty(childPaths)) {
            return parentPath + "/001";
        }
        if (childPaths.size() + 1 >= 1000) {
            throw new RuntimeException("同级机构最多支持9999个");
        }
        //获取同级最大值路径
        Collections.sort(childPaths, Comparator.reverseOrder());
        String maxPath = childPaths.get(0);
        if (StringUtils.isNotBlank(maxPath)) {
            return PathUtils.getNextPath(maxPath);
        }
        return parentPath + "/001";
    }
    public static void main(String[] args) {
        
        String pathNames = "/产品研发中心/业务中台部";
        String substring = pathNames.substring(pathNames.lastIndexOf("/") + 1);
        System.out.println(substring);
        //String paths = "/001/002/003/004/";
        String paths = "/001/001";
        List<String> parentPaths = getParentPaths(paths);
        System.out.println(parentPaths);
    }
    public static String getMaxPath(List<String> pathList) {
        List<Integer> result = new ArrayList<>();
        pathList.forEach(item -> {
            result.add(Integer.valueOf(item.substring(1)));
            ;
        });
        Integer max = Collections.max(result);
        return String.valueOf("/" + max);
    }
}

最后写个接口模拟下

     //localhost:8087/getAllDepart
    @GetMapping("/getAllDepart")
    public Object getAllDepart() {
       
        return departService.importDepart();
    }

运行上面的main程序,观察控制台输出结果

用格式化工具处理下再看,这即为我们期待的结果,实际业务中,只需要在 getAllDepart 这个方法中,将获取数据从数据库查询即可;

[
    {
        "departId":"e1c6d8ba4a504b7da85472ca713be107",
        "pid":"0",
        "name":"产品研发中心",
        "path":null,
        "pname":"",
        "children":[
            {
                "departId":"8e39b272531449ca96c0668ae60d2c2f",
                "pid":"e1c6d8ba4a504b7da85472ca713be107",
                "name":"业务中台部",
                "path":null,
                "pname":"产品研发中心",
                "children":[
 
                ]
            },
            {
                "departId":"ecfe24e1769248df885287c7e153f9e6",
                "pid":"e1c6d8ba4a504b7da85472ca713be107",
                "name":"技术中台部",
                "path":null,
                "pname":"产品研发中心",
                "children":[
                    {
                        "departId":"0218c648abdf4867ad5ea1e99098d526",
                        "pid":"ecfe24e1769248df885287c7e153f9e6",
                        "name":"产品A组",
                        "path":null,
                        "pname":"技术中台部",
                        "children":[
 
                        ]
                    }
                ]
            }
        ]
    },
    {
        "departId":"843bfa6b371e4d7d8d44894d939ca0a5",
        "pid":"0",
        "name":"总裁办",
        "path":null,
        "pname":"",
        "children":[
            {
                "departId":"12dc458b6996484394e2026d5b0f547e",
                "pid":"843bfa6b371e4d7d8d44894d939ca0a5",
                "name":"品牌管理部",
                "path":null,
                "pname":"总裁办",
                "children":[
 
                ]
            }
        ]
    }
]

数据导入

其实,只要按照上文的处理思路做即可,但是这里提一个在逻辑编写过程中遇到的一个比较难处理的问题,即机构的 path 上;

这里必须要说一下这个 path 的事情,path 在真实的业务场景中,是一个非常重要,并且在众多的使用场景中高频使用的字段,因为对一个机构来说,通过业务的区分,这个path一定是唯一的存在; 

仍然使用文章开头的那些数据,最终将 “/产品研发中心/业务一部” 这样的数据入库时,需要将数据组装成一个个对象插入到数据库,同时,插入数据之前,层级也需要组装好,那么对于 “/产品研发中心/业务一部” 这样一条数据,可以想象到,将会产生两个 depart 对象,这里我们考虑下面两个简单的场景;

  • 如果顶级部门不存在,全量导入,比如 “/产品研发中心/业务一部” 这样一条数据,当 “/产品研发中心” 不存在时,完整导入;
  • “/产品研发中心/业务一部” ,当  “产品研发中心” 存在时,只需导入 “业务一部” ;

下面来看核心代码

@Service
public class DepartTest {
 
    @Autowired
    private DepartDao departDao;
 
    @Autowired
    private TransactionUtils transactionUtils;
 
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
 
    static List<String> departLists = new ArrayList<>();
 
    private static final String tenantId = "e10adc3949ba59abbe56e057f20f88dd";
 
    static {
        departLists.add("/top1");
        departLists.add("/top1/ch1");
        departLists.add("/top1/ch1/ch2");
 
    }
 
    public static List<String> getFullNames(String departName) {
        List<String> result = new ArrayList<>();
        String[] splitNames = departName.split("/");
        for (int i = 0; i < splitNames.length; i++) {
            if (!StringUtils.isEmpty(splitNames[i])) {
                result.add(splitNames[i]);
            }
        }
        return result;
    }
 
 
    public List<DepartDTO> getAllDepart() {
 
        List<DepartDTO> departDTOS = new ArrayList<>();
        //保存 部门名称和部门ID的映射关系
        Map<String, String> nameDepartIdMap = new HashMap<>();
        List<Depart> dbExistDepart = new ArrayList<>();
        List<Depart> newDeparts = new ArrayList<>();
 
        for (String single : departLists) {
            //全部的部门名称
            List<String> fullNames = getFullNames(single);
            //直接父级
            String parentPath = PathUtils.getParentPath(single);
            //处理顶级的部门数据【只有自己本身,比如 "/总裁办"】
            if (StringUtils.isEmpty(parentPath)) {
                //1、说明当前只有一级,即顶级数据
                //2、如果是顶级数据,则需要判断数据库是否存在,如果已经存在,不用管,如果不存在,生成新的相关数据
                Depart depart = departDao.getTopDepartByName(fullNames.get(0));
                if (depart != null) {
                    nameDepartIdMap.put(fullNames.get(0), depart.getDepartId());
                    //确认数据库已经存在过的,后面只需要新建部门与用户的关系即可
                    dbExistDepart.add(depart);
                    continue;
                }
                //如果数据不存在,新生成
                String departId = random();
                Depart newDepart = new Depart();
                newDepart.setDepartId(departId);
                newDepart.setName(fullNames.get(0));
                newDepart.setPid("0");
                newDepart.setPath(DepartUtils.getNextPath(tenantId, "0"));
                TransactionStatus transaction = transactionUtils.getTransaction();
                try {
                    departDao.insert(newDepart);
                    //设置手动提交事务
                    dataSourceTransactionManager.commit(transaction);
                } catch (Exception e) {
                    dataSourceTransactionManager.rollback(transaction);
                }
                newDeparts.add(newDepart);
                nameDepartIdMap.put(fullNames.get(0), departId);
                continue;
            }
 
            //如果是非顶级的,则需要拆开  /产品研发中心/技术中台部/产品A组
            for (int i = 0; i < fullNames.size(); i++) {
                String currentDepart = fullNames.get(i);
                //遍历的时候从顶级开始
                if (nameDepartIdMap.containsKey(currentDepart))
                    continue;
 
                if (i == 0) {
                    TransactionStatus transaction = transactionUtils.getTransaction();
                    //仍然是顶级,需要先查数据库
                    Depart topDepart = departDao.getTopDepartByName(currentDepart);
                    if (topDepart != null) {
                        nameDepartIdMap.put(fullNames.get(0), topDepart.getDepartId());
                        dbExistDepart.add(topDepart);
                    } else {
                        //如果数据不存在,新生成
                        String departId = random();
                        Depart _depart = new Depart();
                        _depart.setDepartId(departId);
                        _depart.setName(fullNames.get(0));
                        _depart.setTenantId(tenantId);
                        _depart.setPid("0");
                        _depart.setPath(DepartUtils.getNextPath(tenantId, "0"));
                        try {
                            departDao.insert(_depart);
                            //设置手动提交事务
                            dataSourceTransactionManager.commit(transaction);
                        } catch (Exception e) {
                            dataSourceTransactionManager.rollback(transaction);
                        }
                        if (fullNames.size() == 1) {
                            newDeparts.add(_depart);
                        }
                    }
                    continue;
                }
                //处理其他层级数据
                String parentName = parentPath.substring(parentPath.lastIndexOf("/") + 1);
                //开启一个新的事务
                TransactionStatus transaction = transactionUtils.getTransaction();
                //判断自身是否已经存在了
                Depart dbCurrentDepart = departDao.getDepartByNameAndPid(currentDepart, nameDepartIdMap.get(parentName));
                if (dbCurrentDepart != null) {
                    //如果已经存在了,直接跳过
                    dbExistDepart.add(dbCurrentDepart);
                    nameDepartIdMap.put(currentDepart, dbCurrentDepart.getDepartId());
                    dataSourceTransactionManager.commit(transaction);
                    continue;
                }
 
                Depart _depart = new Depart();
                _depart.setTenantId(tenantId);
                String departId = random();
                _depart.setDepartId(departId);
                _depart.setName(currentDepart);
                String pid = nameDepartIdMap.get(parentName);
 
                //判断上级部门在数据库是否真的存在
                Depart directParent = departDao.getDepartById(nameDepartIdMap.get(parentName));
 
                boolean isCurrentDepartDbExist = false;
                if (directParent != null) {
                    _depart.setPid(pid);
                    String nextPath = DepartUtils.getNextPath(tenantId, pid);
                    _depart.setPath(nextPath);
                    departDao.insert(_depart);
                    dataSourceTransactionManager.commit(transaction);
                    //父级存在时
                    nameDepartIdMap.put(currentDepart, departId);
                }
 
                //如果是最后的那一个,才是本次实际要关联的部门数据
                if (i == fullNames.size() - 1) {
                    if (isCurrentDepartDbExist) {
                        dbExistDepart.add(_depart);
                        nameDepartIdMap.put(currentDepart, departId);
                        continue;
                    }
                    newDeparts.add(_depart);
                    nameDepartIdMap.put(currentDepart, departId);
                }
            }
        }
        return departDTOS;
    }
 
 
    public static String random() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
}

在代码编写过程中,有一个比较难处理的问题,就是在读取外部数据,组装depart 的path的时候,为什么这么讲呢?

要知道,根据上面描述的两种实现情况,path 可能需要动态组装而成,很多同学可能会说,可以先把depart对象全部组装完成,最后再通过层级关系构建出 path 的完整路径;

事实上,一开始我也是这么想的,但是最终发现这样走不通,原因就在于 顶级部门 “/产品研发中心”在数据库中可能存在,也可能不存在,而 path的生成一定是需要结合数据库的某些业务字段动态查询而构造出来的;

所以如果先组装完成数据再构建path,这样带来的问题复杂性将会大大增加;

那么比较可行的而且可以实现的方式就是,在组装数据的过程中,动态查库进行组装数据;

但是小编在编码的时候发现,如果使用SpringBoot工程自身的事务管理器的话,无论是哪种事务隔离级别,都将无法满足这样一个需求,即 “前一步将父级部门数据插入,子部门能够查到父级的数据”这样一个问题;

所以为了达到这个目的,这里采用了 jdbc 手动管理事务的方式进行;

那么通过上面的方式,就可以实现层级数据的导入效果了,具体逻辑可以参考注释说明进行理解;

到此这篇关于Java 处理树形结构数据的文章就介绍到这了,更多相关java 树形结构数据内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java 处理树形结构数据的过程

本文链接: https://lsjlt.com/news/171394.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Java 处理树形结构数据的过程
    目录前言处理过程接下来分析下具体的实现;1、导入的时候以层级保存数据 2、返回树形层级结构数据返回树形层级数据数据导入前言 问题的背景大概是这样的,有下面这样一个excel...
    99+
    2022-11-13
    java 树形结构数据 java 结构数据
  • JavaScript树形数据结构处理
    目录树形数据的一些相关处理方法1. 递归查找当前节点2. 递归获取当前节点及以下的所有节点id3. 递归判断所有后代节点中有无此节点中的一个4. 递归树形数据扁平化5. 扁平化数据转...
    99+
    2024-04-02
  • JAVA如何把数据库的数据处理成树形结构
    目录前言😎实现思路😎完整代码总结-核心代码前言 不知道大家在做项目的时候有没有接触到将平平无奇数据结合处理成有层次的数据呢,类似下面这样 或者 ...
    99+
    2024-04-02
  • Java获取树形结构数据
    目录 前言: 开发前准备: 数据库: 实体类: VO对象: 代码实现: Controller层: Service层: 运行结果: 第二种 前言: 在日常的开发或者工作需求中,我们会用到树形结构数据。树形结构是一个比较常用的数据类型,一般多用...
    99+
    2023-09-02
    java 开发语言
  • java递归实现树形结构数据
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、树形结构是什么?二、实现方案1、stream流递归实现1.1 实体类1.2 实现类 2、jdk1.7以下实现2.1 节点类2.2 实现类...
    99+
    2023-08-18
    java 数据库 mysql
  • JS使用reduce()方法处理树形结构数据
    目录定义语法实例1. 没有传递初始值init2. 传递初始值的情况下3. 数组去重4. 利用 reduce 对数组中的 Object 对象进行分组及合并5. 利用 reduce 处理...
    99+
    2024-04-02
  • 【Java 数据结构】树和二叉树
    篮球哥温馨提示:编程的同时不要忘记锻炼哦! 一棵倒立过来的树.  目录 1、什么是树? 1.1 简单认识树  1.2 树的概念  1.3 树的表示形式 2、二叉树 2.1 二叉树的概念 2.2 特殊的二叉树...
    99+
    2023-09-17
    算法 数据结构
  • Java 递归查询部门树形结构数据的实践
    说明:在开发中,我们经常使用树形结构来展示菜单选项,如图: 那么我们在后端怎么去实现这样的一个功能呢? 1、数据库表:department 2、编写sql映射语句 &...
    99+
    2024-04-02
  • java递归实现树形结构数据完整案例
    目录前言一、树形结构是什么?二、实现方案1、stream流递归实现1.1 实体类1.2 实现类2、jdk1.7以下实现2.1 节点类2.2 实现类3、应用场景3.1 用于前端方便展示...
    99+
    2023-05-17
    Java遍历树形结构 java递归详解 java树形数据结构
  • PHP数据结构:树形结构的探索,掌握层级数据的组织
    树形结构是一种分层组织数据的非线性结构,在 php 中可用递归或迭代方式表示和遍历。表示方法有递归(使用 class)和迭代(使用数组);遍历方式有递归遍历和迭代遍历(使用栈)。实战案例...
    99+
    2024-05-14
    php数据结构 树形结构
  • springboot构造树形结构数据并查询的方法
    因为项目需要,页面上需要树形结构的数据进行展示(类似下图这样),因此需要后端返回相应格式的数据。 不说废话,直接开干!!! 我这里用的是springboot+mybatis-pl...
    99+
    2024-04-02
  • Java数据结构学习之树
    目录一、树1.1 概念1.2 术语1.3 树的实现1.3.1 用数组来实现一棵树?1.3.2 用链表实现一棵树?1.3.3 树的转化1.4 二叉树1.4.1 二叉树的性质1.4.2 ...
    99+
    2024-04-02
  • Mysql树形结构的数据库表设计方案
    目录前言一、基本数据 二、继承关系驱动的设计 三、基于左右值编码的设计四、树形结构CRUD算法(1)获取某节点的子孙节点 (2)获取某节点的族谱路径(3)为某节点添加子孙节点 (4)...
    99+
    2024-04-02
  • MySQL查询树形结构数据的两种方法
    目录1. 递归查询2. 闭包表对于mysql查询树形结构,可以使用递归查询或者闭包表来实现。以下是两种常用的方法: 1. 递归查询 使用递归查询可以遍历树形结构,获取父节点和子节点的关系。假设有一个名为 your_tab...
    99+
    2023-11-11
    MySQL查询树形数据 MySQL查询树形结构 MySQL树形结构查询
  • springboot vue接口测试HutoolUtil TreeUtil处理树形结构
    目录基于springboot+vue的测试平台开发一、引用 HutoolUtil二、建表三、后端接口实现1. Controller 层2. DAO层3. Service 层四、测试一...
    99+
    2024-04-02
  • Java实现树形结构的示例代码
    目录前言数据库表结构实现思路具体代码1、造数据,和数据库表数据一致2、树型结构实体类前言 由于业务需要,后端需要返回一个树型结构给前端,包含父子节点的数据已经在数据库中存储好,现在需...
    99+
    2024-04-02
  • 如何处理MySQL中的树形数据
    在MySQL中处理树形数据通常使用两种方法:邻接表模型和闭包表模型。 邻接表模型: 邻接表模型是最简单和最常见的处理树形数据的方法...
    99+
    2024-04-30
    MySQL
  • JavaScript数组扁平转树形结构数据(Tree)的实现
    前言 之前面试有遇到过这个问题,面试官问:如何把一个数组数据扁平,然后转化为Tree结构数据,工作中刚好也用到了,在这里总结一下。 需求大致如下 把这个数组转为树形结构数据(Tree...
    99+
    2022-11-13
    JavaScript数组扁平转树形结构 javascript扁平数组转树形结构
  • Java数据结构之线段树的原理与实现
    目录简介实现思路节点定义构建线段树求解区间和更新线段树简介 线段树是一种二叉搜索树,是用来维护区间信息的数据结构。可以在O(logN)的时间复杂度内实现单点修改、区间修改、区间查询(...
    99+
    2024-04-02
  • Java数据结构之红黑树的原理及实现
    目录为什么要有红黑树这种数据结构红黑树的简介红黑树的基本操作之旋转红黑树之添加元素红黑树之删除结点删除结点没有儿子的情况删除结点仅有一个儿子结点的情况删除结点有两个儿子结点红黑树动态...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作