当前关注:"树形List"与"扁平List"互转(Java实现)

首页>资讯 > 正文
2023-05-11 10:38:54

来源:博客园

背景:在平时的开发中,我们时常会遇到下列场景

公司的组织架构的数据存储与展示文件夹层级的数据存储与展示评论系统中,父评论与诸多子评论的数据存储与展示......

对于这种有层级的结构化数据,就像是一棵一样。在关系型数据库中,通常将一个个的节点信息存储到表中,通过一个字段(例如,pid),指向其父节点。而在数据展示的时候,我们又希望它是呈现层级的,例如:

id  name        pid1   总公司       -12   上海分公司    13   浙江分公司    14   闵行区分公司  25   嘉兴分公司    3{    "id": 1,    "name": "总公司",    "pid": -1,    "branch":    [        {            "id": 2,            "name": "上海分公司",            "pid": 1,            "branch":            [                {                    "id": 4,                    "name": "闵行区分公司",                    "pid": 2,                    "branch":                    []                }            ]        },        {            "id": 3,            "name": "浙江分公司",            "pid": 1,            "branch":            [                {                    "id": 5,                    "name": "嘉兴分公司",                    "pid": 3,                    "branch":                    []                }            ]        }    ]}

所以,本文的主要内容就是提供几种方案,实现这两类数据的转换方式。


(资料图)

内容导览

存储树的表结构扁平List转树形List双层for递归转换为Map栈树形List转扁平List递归栈存储树的表结构

如何在一张数据库表中表示一颗树结构中的所有节点信息,这里有一个示例:

DROP TABLE IF EXISTS zj_city;CREATE TABLE zj_city (    id BIGINT NOT NULL AUTO_INCREMENT,    name VARCHAR(50) COMMENT "节点名称",    pid int NOT NULL COMMENT "父节点",    create_time DATETIME DEFAULT now() COMMENT "创建时间: 年-月-日 时:分:秒",    update_time DATETIME DEFAULT now() ON UPDATE now() COMMENT "更新时间",    is_deleted BIT DEFAULT 0 COMMENT "是否删除:0-false-未删除;1-true-已删除",    PRIMARY KEY (id))COMMENT "浙江城市";INSERT INTO zj_city(name, pid) VALUES("浙江省",0),("金华市",1),("嘉兴市",1),("杭州市",1),("宁波市",1);INSERT INTO zj_city(name, pid) VALUES("下城区",4),("钱塘区",4),("西湖区",4),("上城区",4);INSERT INTO zj_city(name, pid) VALUES("南湖区",3),("秀洲区",3),("桐乡市",3),("平湖市",3),("海宁市",3);INSERT INTO zj_city(name, pid) VALUES("梧桐街道",12),("凤鸣街道",12),("龙翔街道",12),("崇福镇",12),("乌镇镇",12),("高桥镇",12),("河山镇",12),("濮院镇",12);SELECT * from zj_city;
扁平List转树形List

应用场景

公司组织结构省市级评论系统中,父评论与诸多子评论其他层级展示的数据双层for

主要思想:外层循环-找父节点;内层循环-找子节点;因为每个元素都会找一遍,所有最终得到完整的树

import com.alibaba.fastjson.JSON;import com.ks.boot.entity.CityEntity;import org.junit.jupiter.api.BeforeEach;import org.junit.jupiter.api.Test;import java.util.ArrayList;import java.util.List;public class TreeListDemo {    List cityEntities;    /**     * id  name  pid     * 1   浙江   0     * 2   杭州   1     * 3   嘉兴   1     * 4   南湖   3     * 5   桐乡   3     * 6   余杭   2     * 7   西湖   2     * 8   云南   0     * 9   昆明   8     * 10  昭通   8     */    @BeforeEach    public void init() {        cityEntities = JSON.parseArray("[{\"id\":1,\"name\":\"浙江\",\"pid\":0},\n" +                "{\"id\":2,\"name\":\"杭州\",\"pid\":1},\n" +                "{\"id\":3,\"name\":\"嘉兴\",\"pid\":1},\n" +                "{\"id\":4,\"name\":\"南湖\",\"pid\":3},\n" +                "{\"id\":5,\"name\":\"桐乡\",\"pid\":3},\n" +                "{\"id\":6,\"name\":\"余杭\",\"pid\":2},\n" +                "{\"id\":7,\"name\":\"西湖\",\"pid\":2},\n" +                "{\"id\":8,\"name\":\"云南\",\"pid\":0},\n" +                "{\"id\":9,\"name\":\"昆明\",\"pid\":8},\n" +                "{\"id\":10,\"name\":\"昭通\",\"pid\":8}]", CityEntity.class);    }    @Test    public void testList2Tree() {        List resultTree = list2Tree(this.cityEntities);        System.out.println(JSON.toJSONString(resultTree));    }    /**     * 双层for,List转Tree     * 主要思想:外层循环-找父节点;内层循环-找子节点;因为每个元素都会找一遍,所有最终得到完整的树     * 时间复杂度:N^2;空间复杂度:N     */    public List list2Tree(List cityEntities) {        List resultCities = new ArrayList<>();        for (CityEntity city : cityEntities) {            if(0 == city.getPid()) { //根节点、顶级节点,直接放入最终返回结果的List                resultCities.add(city);            }            for (CityEntity curCity : cityEntities) { //根据当前city找它的子节点                if(city.getId().equals(curCity.getPid())) {                    if(city.getSubCityList() == null) { //还没有任何子节点,new一个空的放进去                        city.setSubCityList(new ArrayList<>());                    }                    city.getSubCityList().add(curCity);                }            }        }        return resultCities;    }}public class CityEntity {    private Long id;    private String name;    private Long pid;    private List subCityList;    getter/setter}
递归

主要思想:获取所有根节点、顶级节点,再从List中查找根节点的子节点;

import com.alibaba.fastjson.JSON;import com.ks.boot.entity.CityEntity;import org.junit.jupiter.api.BeforeEach;import org.junit.jupiter.api.Test;import java.util.ArrayList;import java.util.List;public class TreeListDemo {    List cityEntities;    /**     * id  name  pid     * 1   浙江   0     * 2   杭州   1     * 3   嘉兴   1     * 4   南湖   3     * 5   桐乡   3     * 6   余杭   2     * 7   西湖   2     * 8   云南   0     * 9   昆明   8     * 10  昭通   8     */    @BeforeEach    public void init() {        cityEntities = JSON.parseArray("[{\"id\":1,\"name\":\"浙江\",\"pid\":0},\n" +                "{\"id\":2,\"name\":\"杭州\",\"pid\":1},\n" +                "{\"id\":3,\"name\":\"嘉兴\",\"pid\":1},\n" +                "{\"id\":4,\"name\":\"南湖\",\"pid\":3},\n" +                "{\"id\":5,\"name\":\"桐乡\",\"pid\":3},\n" +                "{\"id\":6,\"name\":\"余杭\",\"pid\":2},\n" +                "{\"id\":7,\"name\":\"西湖\",\"pid\":2},\n" +                "{\"id\":8,\"name\":\"云南\",\"pid\":0},\n" +                "{\"id\":9,\"name\":\"昆明\",\"pid\":8},\n" +                "{\"id\":10,\"name\":\"昭通\",\"pid\":8}]", CityEntity.class);    }    /**     * 递归,List转Tree     * 主要思想:获取所有根节点、顶级节点,再从List中查找根节点的子节点;     * 时间复杂度:N*(1+N)/2,O(N^2),因为递归方法中,最好是List的第一元素就是要找的子节点,最坏是     * List的最后一个元素是子节点     */    @Test    public void testList2Tree() {        List resultCities = new ArrayList<>();        for (CityEntity city : cityEntities) {            if(0 == city.getPid()) { //获取所有根节点、顶级节点,再根据根节点进行递归                CityEntity topCity = findChild(cityEntities, city); //此时的topCity已经包含它的所有子节点                resultCities.add(topCity);            }        }        System.out.println(JSON.toJSONString(resultCities));    }    /**     *     * @param cityEntities 在那个里面找     * @param curCity 找谁的子节点     * @return curCity的子节点     */    public CityEntity findChild(List cityEntities, CityEntity curCity) {        for (CityEntity city : cityEntities) {            if(curCity.getId().equals(city.getPid())) {                if(null == curCity.getSubCityList()) {                    curCity.setSubCityList(new ArrayList<>());                }                CityEntity subChild = findChild(cityEntities, city); //每次递归,都从头开始查找有没有city的子节点                curCity.getSubCityList().add(subChild);            }        }        return curCity;    }}public class CityEntity {    private Long id;    private String name;    private Long pid;    private List subCityList;    getter/setter}
转换为Map

主要思想

在双层for的解法中,由于内层for也需要遍历以便List,造成时间复杂度上身为平方级如果内层for不需要遍历完整的List,而是事先预处理到Map中,寻找时直接从Map中获取,则时间复杂度降为LogN
import com.alibaba.fastjson2.JSON;import org.junit.jupiter.api.BeforeEach;import org.junit.jupiter.api.Test;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.stream.Collectors;public class TreeListDemo {    List cityEntities;    /**     * id  name  pid     * 1   浙江   0     * 2   杭州   1     * 3   嘉兴   1     * 4   南湖   3     * 5   桐乡   3     * 6   余杭   2     * 7   西湖   2     * 8   云南   0     * 9   昆明   8     * 10  昭通   8     */    @BeforeEach    public void init() {        cityEntities = JSON.parseArray("[{\"id\":1,\"name\":\"浙江\",\"pid\":0},\n" +                "{\"id\":2,\"name\":\"杭州\",\"pid\":1},\n" +                "{\"id\":3,\"name\":\"嘉兴\",\"pid\":1},\n" +                "{\"id\":4,\"name\":\"南湖\",\"pid\":3},\n" +                "{\"id\":5,\"name\":\"桐乡\",\"pid\":3},\n" +                "{\"id\":6,\"name\":\"余杭\",\"pid\":2},\n" +                "{\"id\":7,\"name\":\"西湖\",\"pid\":2},\n" +                "{\"id\":8,\"name\":\"云南\",\"pid\":0},\n" +                "{\"id\":9,\"name\":\"昆明\",\"pid\":8},\n" +                "{\"id\":10,\"name\":\"昭通\",\"pid\":8}]", CityEntity.class);    }    /**     * 在双层for的解法中,由于内层for也需要遍历以便List,造成时间复杂度上身为平方级     * 如果内层for不需要遍历完整的List,而是事先预处理到Map中,寻找时直接从Map中获取,则时间复杂度降为LogN     */    @Test    public void list2tree() {        //收集每个PID下的节点为Map        Map> cityMapByPid = cityEntities.stream().collect(Collectors.groupingBy(CityEntity::getPid));        List resultCityList = new ArrayList<>();        for (CityEntity city : cityEntities) {            if(0 == city.getPid()) { //根节点、顶点                resultCityList.add(city);            }            List citiesByPid = cityMapByPid.get(city.getId());            if(null != citiesByPid && citiesByPid.size() > 0) { //有子节点                if(null == city.getSubCityList()) {                    city.setSubCityList(new ArrayList<>());                }                city.getSubCityList().addAll(citiesByPid); //塞入            }        }        System.out.println(JSON.toJSONString(resultCityList));    }    /**     * 简化写法:在收集到Map时,对于没有子节点的节点,创建一个空的塞入到Map中     */    @Test    public void list2tree4Simple() {        List resultCityList = new ArrayList<>();        //保存每个PID下的子节点        Map> cityMapByPid = new HashMap<>();        for (CityEntity city : cityEntities) { //收集每个PID下的子节点            //获取当前PID对应的子节点List,如果没有则创建一个空的List塞入            //这个设计得很巧妙            List children = cityMapByPid.getOrDefault(city.getPid(), new ArrayList<>());            children.add(city); //插入当前元素            cityMapByPid.put(city.getPid(), children);        }        for (CityEntity city : cityEntities) {            if(0 == city.getPid()) { //根节点、顶点                resultCityList.add(city);            }            city.setSubCityList(cityMapByPid.get(city.getId()));        }        System.out.println(JSON.toJSONString(resultCityList));    }}

主要思想

收集根节点、顶级节点,存入resultList,并且压栈循环出栈,栈元素Cur找Cur的所有子元素为child如果child不为空,则再压入栈中。这一步的目的是,再一次找child的子元素
import com.alibaba.fastjson2.JSON;import org.junit.jupiter.api.BeforeEach;import org.junit.jupiter.api.Test;import java.util.*;import java.util.stream.Collectors;public class TreeListDemo {    List cityEntities;    /**     * id  name  pid     * 1   浙江   0     * 2   杭州   1     * 3   嘉兴   1     * 4   南湖   3     * 5   桐乡   3     * 6   余杭   2     * 7   西湖   2     * 8   云南   0     * 9   昆明   8     * 10  昭通   8     */    @BeforeEach    public void init() {        cityEntities = JSON.parseArray("[{\"id\":1,\"name\":\"浙江\",\"pid\":0},\n" +                "{\"id\":2,\"name\":\"杭州\",\"pid\":1},\n" +                "{\"id\":3,\"name\":\"嘉兴\",\"pid\":1},\n" +                "{\"id\":4,\"name\":\"南湖\",\"pid\":3},\n" +                "{\"id\":5,\"name\":\"桐乡\",\"pid\":3},\n" +                "{\"id\":6,\"name\":\"余杭\",\"pid\":2},\n" +                "{\"id\":7,\"name\":\"西湖\",\"pid\":2},\n" +                "{\"id\":8,\"name\":\"云南\",\"pid\":0},\n" +                "{\"id\":9,\"name\":\"昆明\",\"pid\":8},\n" +                "{\"id\":10,\"name\":\"昭通\",\"pid\":8}]", CityEntity.class);    }    /**     * 主要思想:     *  收集根节点、顶级节点,存入resultList,并且压栈     *  循环出栈,栈元素Cur     *      找Cur的所有子元素为child     *      如果child不为空,则再压入栈中。这一步的目的是,再一次找child的子元素     * 时间复杂度:N(过滤出所有跟节点) + 常数(出栈) * N(遍历List找当前节点的子元素)     */    @Test    public void list2tree() {        List resultCityList = new ArrayList<>();        Stack stack = new Stack<>();        resultCityList = cityEntities.stream().filter(ele -> 0 == ele.getPid()).collect(Collectors.toList());        stack.addAll(resultCityList); //根节点、顶点入栈        while(!stack.isEmpty()) {            CityEntity curCity = stack.pop();            List child = cityEntities.stream().filter(ele -> curCity.getId().equals(ele.getPid())).collect(Collectors.toList());            if(!child.isEmpty()) { //这一步处理的原因是:当没有子元素,不显示该个字段。流处理后没有元素只会返回空List,不会返回null                curCity.setSubCityList(child);            }            if(!child.isEmpty()) {                stack.addAll(child);            }        }        System.out.println(JSON.toJSONString(resultCityList));    }}
树形List转扁平List递归

主要思想:遍历树节点,一个树节点如果有子树,则再次递归此子树,直到没有子树为止

import com.alibaba.fastjson2.JSON;import org.junit.jupiter.api.BeforeEach;import org.junit.jupiter.api.Test;import java.util.ArrayList;import java.util.List;/** * id  name  pid * 1   浙江   0 * 2   杭州   1 * 3   嘉兴   1 * 4   南湖   3 * 5   桐乡   3 * 6   余杭   2 * 7   西湖   2 * 8   云南   0 * 9   昆明   8 * 10  昭通   8 */public class ListTreeDemo {    List treeList;    @BeforeEach    public void init() {        treeList = JSON.parseArray("[{\"id\":1,\"name\":\"浙江\",\"pid\":0,\"subCityList\":[" +                "{\"id\":2,\"name\":\"杭州\",\"pid\":1,\"subCityList\":[{\"id\":6,\"name\":\"余杭\",\"pid\":2},{\"id\":7,\"name\":\"西湖\",\"pid\":2}]}," +                "{\"id\":3,\"name\":\"嘉兴\",\"pid\":1,\"subCityList\":[{\"id\":4,\"name\":\"南湖\",\"pid\":3},{\"id\":5,\"name\":\"桐乡\",\"pid\":3}]}]}," +                "{\"id\":8,\"name\":\"云南\",\"pid\":0,\"subCityList\":[{\"id\":9,\"name\":\"昆明\",\"pid\":8},{\"id\":10,\"name\":\"昭通\",\"pid\":8}]}]", CityEntity.class);    }    @Test    public void tree2list() {        List resList = new ArrayList<>();        //这一层for的目的是:遍历根节点        for (CityEntity city : treeList) {            reversion(city,resList);        }        System.out.println(JSON.toJSONString(resList));    }    public void reversion(CityEntity curNode, List resList) {        resList.add(beanCopy(curNode));        List subCityList = curNode.getSubCityList();        if(subCityList != null && !subCityList.isEmpty()) {            for (CityEntity city : subCityList) { //递归寻找子节点的子节点们                reversion(city, resList);            }        }                //递归的出口就是subCityList为null或者empty    }    private CityEntity beanCopy(CityEntity source) {        CityEntity res = new CityEntity();        res.setId(source.getId());        res.setName(source.getName());        res.setPid(source.getPid());        return res;    }}

主要思想

依次遍历树形List,当前节点为Cur将Cur收集到某个存储结果的List如果Cur有子树,压入某个栈中依次弹出栈元素,当前弹出的元素为StackSubTree如果StackSubTree还有子树,继续压栈如果StackSubTree没有子树,则放入结果List
import com.alibaba.fastjson2.JSON;import org.junit.jupiter.api.BeforeEach;import org.junit.jupiter.api.Test;import java.util.ArrayList;import java.util.List;import java.util.Stack;/** * id  name  pid * 1   浙江   0 * 2   杭州   1 * 3   嘉兴   1 * 4   南湖   3 * 5   桐乡   3 * 6   余杭   2 * 7   西湖   2 * 8   云南   0 * 9   昆明   8 * 10  昭通   8 */public class ListTreeDemo {    List treeList;    @BeforeEach    public void init() {        treeList = JSON.parseArray("[{\"id\":1,\"name\":\"浙江\",\"pid\":0,\"subCityList\":[" +                "{\"id\":2,\"name\":\"杭州\",\"pid\":1,\"subCityList\":[{\"id\":6,\"name\":\"余杭\",\"pid\":2},{\"id\":7,\"name\":\"西湖\",\"pid\":2}]}," +                "{\"id\":3,\"name\":\"嘉兴\",\"pid\":1,\"subCityList\":[{\"id\":4,\"name\":\"南湖\",\"pid\":3},{\"id\":5,\"name\":\"桐乡\",\"pid\":3}]}]}," +                "{\"id\":8,\"name\":\"云南\",\"pid\":0,\"subCityList\":[{\"id\":9,\"name\":\"昆明\",\"pid\":8},{\"id\":10,\"name\":\"昭通\",\"pid\":8}]}]", CityEntity.class);    }    /**     * 1. 依次遍历树形List,当前节点为Cur     *   a) 将Cur收集到某个存储结果的List     *   b) 如果Cur有子树,压入某个栈中     * 2. 依次弹出栈元素,当前弹出的元素为StackSubTree     *   a) 如果StackSubTree还有子树,继续压栈     *   b) 如果StackSubTree没有子树,则放入结果List     */    @Test    public void tree2list() {        List resList = new ArrayList<>();        Stack> stack = new Stack<>();        for (CityEntity curCity : treeList) {            resList.add(beanCopy(curCity));            if (curCity.getSubCityList() != null && !curCity.getSubCityList().isEmpty()) {                stack.push(curCity.getSubCityList());            }        }        while (!stack.isEmpty()) {            List subTree = stack.pop();            for (CityEntity city : subTree) {                if (city.getSubCityList() != null && !city.getSubCityList().isEmpty()) {                    stack.push(city.getSubCityList());                } else {                    resList.add(beanCopy(city));                }            }        }        System.out.println(JSON.toJSONString(resList));    }    private CityEntity beanCopy(CityEntity source) {        CityEntity res = new CityEntity();        res.setId(source.getId());        res.setName(source.getName());        res.setPid(source.getPid());        return res;    }}

标签:

THE END
免责声明:本文系转载,版权归原作者所有;旨在传递信息,不代表热讯制鞋网的观点和立场。

相关热点

新华社电 上海市文化和旅游局近日发布《上海市密室剧本杀内容备案管理规定(征求意见稿)》,并截至12月8日面向社会公众广泛征求意见。这
2021-11-19 13:46:03
《中国证券报》17日刊发文章《备战2022 基金经理调仓换股布新局》。文章称,距离2021年结束仅剩一个多月,基金业绩分化明显。部分排名靠前
2021-11-19 13:46:03
交通运输部办公厅 中国人民银行办公厅 中国银行保险监督管理委员会办公厅关于进一步做好货车ETC发行服务有关工作的通知各省、自治区、直
2021-11-19 13:45:58
新华社北京11月17日电 题:从10月份市场供需积极变化看中国经济韧性新华社记者魏玉坤、丁乐读懂中国经济,一个直观的视角就是市场供需两端
2021-11-19 13:45:58
全国教育财务工作会议披露的消息称,2020年,中国国家财政性教育经费投入达4 29万亿元,占GDP总量的4 206%,我国国家财政性教育经费支出占G
2021-11-19 13:45:48
如果你也热爱“种草”,前方高能预警!让你心心念念、“浏览”忘返的网络平台,可能早已成为一块块“韭菜地”。近日,据《半月谈》报道,有...
2021-11-19 13:45:48
日前,工业和信息化部印发《“十四五”信息通信行业发展规划》(以下简称《规划》),描绘了未来5年信息通信行业的发展趋势。《规划》指出...
2021-11-19 13:45:40
本报讯(中青报·中青网记者 周围围)2021年快递业务旺季正式拉开帷幕。国家邮政局监测数据显示,仅11月1日当日,全国共揽收快递包裹5 69
2021-11-19 13:45:40
人民网曼谷11月17日电 (记者赵益普)17日上午,中国援柬埔寨第七批200万剂科兴新冠疫苗抵达金边国际机场。当天,柬埔寨政府在机场举行了
2021-11-19 13:45:35
金坛压缩空气储能国家试验示范项目主体工程一角受访者供图依托清华大学非补燃压缩空气储能技术,金坛压缩空气储能项目申请专利百余项,建立
2021-11-19 13:45:35
视觉中国供图42亿立方米据有关部门预计,今年山西煤炭产量有望突破12亿吨,12月份山西外送电能力将超过900万千瓦,今冬明春煤层气产量将达4
2021-11-19 13:44:34
14省份相继发布2021年企业工资指导线——引导企业合理提高职工工资今年以来,天津、新疆、内蒙古、陕西、西藏、山东、江西、山西、福建、四
2021-11-19 13:44:34
中新网客户端北京11月18日电 (记者 谢艺观)“一条路海角天涯,两颗心相依相伴,风吹不走誓言,雨打不湿浪漫,意济苍生苦与痛,情牵天下喜
2021-11-19 13:44:31
近日,交通运输部等三部门发布《关于进一步做好货车ETC发行服务有关工作的通知》。通知提到,对不具备授信条件的用户,商业银行可在依法合
2021-11-19 13:44:31
欧莱雅面膜陷优惠“年度最大”风波 涉及该事件集体投诉超6000人次美妆大牌双十一促销翻车?近日,因预售价格比双十一现货贵出66%,欧莱雅
2021-11-19 13:44:13
43 6%受访者会在工作两三年后考虑跳槽54 3%受访者认为跳槽对个人职业发展有利有弊如今对不少年轻人来说,想对一份工作“从一而终”不太容易
2021-11-19 13:44:13
超八成受访青年表示如有机会愿意开展副业 规划能力最重要64 4%受访青年指出做副业跟风心态最要不得如今,“身兼数职”已成为年轻人当中的
2021-11-19 13:44:01
发展氢能正当其时【科学随笔】氢能是一种二次能源,它通过一定的方法利用其他能源制取,具有清洁无污染、可储存、与多种能源便捷转换等优点
2021-11-19 13:44:01
“千杯不醉”的解酒“神药”能信吗?专家:网红“解酒药” 其实不算药俗话说,“酒逢知己千杯少”,酒一直是国人饭桌上至关重要的存在。尽...
2021-11-19 13:43:57
最新文章

相关推荐