编程小技巧: 非递归方式组织树形结构

616 阅读12分钟

编程小技巧: 非递归方式组织树形结构

理论介绍

在以往的构建树型结构,首推实现方式是以递归的方式构建树型结构,但是构建时,需要反复的循环遍历所有节点,这会增加代码的复杂度。如下图所示为递归构建树型结构的伪代码:

​
public class Node {
    private Long id;
​
    private String name;
​
    private Long parentId;
​
    
    private List<Node> children;
    
    // 省略getter/setter方法
}
​
public class NodeTest {
    
    public void nodeBuildTest(){
        // 获取扁平的node集合
        List<Node> nodeList = getNodeList(); 
        // 假设根节点的父级节点为0
        List<Node> nodeTree = buildTree(nodeList,0);
    }
    
    public List<Node> buildTree(List<Node> nodeList,Long parentId){
        List<Node> result = new ArrayList();
        for(Node node : nodeList){
            if(node.getParentId().equals(parentId)){
                buildTree(nodeList,node.getId());
                result.add(node);
            }
        }
        return node;
    }
}

观察可知,代码使用递归的方式构建树型结构,但是每层在递归时都会遍历nodeList集合,如果nodeList中的节点过多,则会导致遍历次数增多,每次都是从头遍历到尾,性能较低,那是否有可近一步的优化?

建树型结构的进一步优化

使用nodeList转换map结构,mapkeynodeidvaluenode,通过map提高查询效率。伪代码实现如下:

public class NodeTest {
    public List<Node> buildTree(List<Node> nodeList,Long parentId){
        // 构建map 时间复杂度为O(n)
        Map<Long,Node> = nodeList.stream()
            // 使用peek初始化每个node节点的子节点集合对象
            .peek(node->node.setChildren(new ArrayList<>()))
            // 构建map对象
            .collect(Collectors.toMap(Node::getId, v -> v));
        // 遍历 nodeList节点 时间复杂度为O(n)
        for (Node node : nodeList) {
            Long parentId = node.getParentId();
            // 通过map对象直接获取当前节点的父级节点 借助map高效的查询效率 将会很高效的查询到父级节点
            Node parent = map.get(parentId);
            // parent对象为空 表示parentId = 0
            if (parent != null) {
                List<Node> children = parent.getChildren();
                children.add(node);
            }
        }
        // 收集parentId = 0的node对象 时间复杂度为O(n)
        return nodeList.stream().filter(node -> node.getParentId() == 0).collect(Collectors.toList());
    }
}

根据上述提供的代码,可将优化方案拆分为三阶段,第一阶段为构建map,第二阶段为关联父子关系,第三阶段为收集。以下分别对这三个阶段作介绍。

构建map对象

为何构建map对象,可类比mysql数据库的索引,nodeList相当于数据库表中的原始数据,map相当于mysql的索引,通过索引去获取某个对象的父级对象时,可提高查找效率,避免了反复遍历nodeList时的低效率问题。构建完map对象后,数据在java内存中的表现形式如下图所示:

map.jpg

mapkeynodeidvalue为对应idnodejava堆中的引用。map相当于mysql的二级索引,通过这样的map,提高了查找效率。

构建父子关系

依次遍历nodeList对象,获取遍历到的当前节点的id,再通过当前节点的idmap中查找当前节点的父节点,如果获取到的当前节点的父节点为空,则说明当前节点为根节点,如果不为空,则将当前节点的引用保存到父节点的子节点集合中。

以上述构建map的元素为例,遍历nodeList集合:

  1. 遍历对象为id:1的节点,parentId = 0,通过map查找id为0的节点,未找到不做任何操作
  2. 遍历对象为id:2的节点,parentId = 0,通过map查找id为0的节点,未找到不做任何操作
  3. 遍历对象为id:3的节点,parentId = 1,通过map查找id为1的节点,获取到id为1的节点,将id为3的节点的引用保存到id为1的集合中,如图所示

children-1.jpg 4. 按步骤三直至遍历完后,即可构建完成所有的父子关系,如图所示

children-2.jpg 由于对象之间通过引用关联,仅需在每个节点的子节点集合中保存所有子节点的引用即可维护所有节点的父子关系。

收集根节点

通过stream过滤收集parentId=0的节点即可。

使用场景

树型结构使用场景比较多,如菜单树,文章的目录树,企业组织的j架构部门信息等。以下以后台管理系统的菜单树举例,实现的相关代码如下

create table sys_menu
(
    id        bigint auto_increment comment '菜单ID'
        primary key,
    name      varchar(50)      not null comment '菜单名称',
    parent_id bigint default 0 null comment '父菜单ID',
    order_num int    default 0 null comment '显示顺序'
)
    comment '菜单权限表';
id,name,parent_id,order_num
1,系统管理,0,1
2,系统监控,0,2
3,系统工具,0,3
100,用户管理,1,1
101,角色管理,1,2
102,菜单管理,1,3
103,部门管理,1,4
104,岗位管理,1,5
105,字典管理,1,6
106,参数设置,1,7
107,通知公告,1,8
108,日志管理,1,9
109,在线用户,2,1
110,定时任务,2,2
111,数据监控,2,3
112,服务监控,2,4
113,缓存监控,2,5
114,缓存列表,2,6
115,表单构建,3,1
116,代码生成,3,2
117,系统接口,3,3
500,操作日志,108,1
501,登录日志,108,2
1000,用户查询,100,1
1001,用户新增,100,2
1002,用户修改,100,3
1003,用户删除,100,4
1004,用户导出,100,5
1005,用户导入,100,6
1006,重置密码,100,7
1007,角色查询,101,1
1008,角色新增,101,2
1009,角色修改,101,3
1010,角色删除,101,4
1011,角色导出,101,5
1012,菜单查询,102,1
1013,菜单新增,102,2
1014,菜单修改,102,3
1015,菜单删除,102,4
1016,部门查询,103,1
1017,部门新增,103,2
1018,部门修改,103,3
1019,部门删除,103,4
1020,岗位查询,104,1
1021,岗位新增,104,2
1022,岗位修改,104,3
1023,岗位删除,104,4
1024,岗位导出,104,5
1025,字典查询,105,1
1026,字典新增,105,2
1027,字典修改,105,3
1028,字典删除,105,4
1029,字典导出,105,5
1030,参数查询,106,1
1031,参数新增,106,2
1032,参数修改,106,3
1033,参数删除,106,4
1034,参数导出,106,5
1035,公告查询,107,1
1036,公告新增,107,2
1037,公告修改,107,3
1038,公告删除,107,4
1039,操作查询,500,1
1040,操作删除,500,2
1041,日志导出,500,3
1042,登录查询,501,1
1043,登录删除,501,2
1044,日志导出,501,3
1045,账户解锁,501,4
1046,在线查询,109,1
1047,批量强退,109,2
1048,单条强退,109,3
1049,任务查询,110,1
1050,任务新增,110,2
1051,任务修改,110,3
1052,任务删除,110,4
1053,状态修改,110,5
1054,任务导出,110,6
1055,生成查询,116,1
1056,生成修改,116,2
1057,生成删除,116,3
1058,导入代码,116,4
1059,预览代码,116,5
1060,生成代码,116,6

java使用mybatis-plus查询sys_menu表的数据

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName("sys_menu")
public class SysMenu implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(type = IdType.AUTO)
    private Long id;
​
    private String name;
​
    private Long parentId;
​
    private Integer orderNum;
​
    @TableField(exist = false)
    private List<SysMenu> children;
}
public interface SysMenuMapper extends BaseMapper<SysMenu> {
}
public class MenuTest {
    @Resource
    private SysMenuMapper sysMenuMapper;
​
    @Test
    public void menuTreeTest() {
        List<SysMenu> menus = sysMenuMapper.selectList(Wrappers.<SysMenu>lambdaQuery().orderByAsc(SysMenu::getOrderNum));
        Map<Long, SysMenu> map = menus.stream()
                .peek(node->node.setChildren(new ArrayList<>()))
                .collect(Collectors.toMap(SysMenu::getId, v -> v));
        for (SysMenu menu : menus) {
            Long parentId = menu.getParentId();
            SysMenu parent = map.get(parentId);
            if (parent != null) {
                List<SysMenu> children = parent.getChildren();
                if (children == null) {
                    children = new ArrayList<>();
                    parent.setChildren(children);
                }
                children.add(menu);
            }
        }
        List<SysMenu> menuList = menus.stream().filter(menu -> menu.getParentId() == 0).collect(Collectors.toList());
    }
}

menuList即为构建完成的菜单树结构,结果如下

[
    {
        "id": 1,
        "name": "系统管理",
        "parentId": 0,
        "orderNum": 1,
        "children": [
            {
                "id": 100,
                "name": "用户管理",
                "parentId": 1,
                "orderNum": 1,
                "children": [
                    {
                        "id": 1000,
                        "name": "用户查询",
                        "parentId": 100,
                        "orderNum": 1
                    },
                    {
                        "id": 1001,
                        "name": "用户新增",
                        "parentId": 100,
                        "orderNum": 2
                    },
                    {
                        "id": 1002,
                        "name": "用户修改",
                        "parentId": 100,
                        "orderNum": 3
                    },
                    {
                        "id": 1003,
                        "name": "用户删除",
                        "parentId": 100,
                        "orderNum": 4
                    },
                    {
                        "id": 1004,
                        "name": "用户导出",
                        "parentId": 100,
                        "orderNum": 5
                    },
                    {
                        "id": 1005,
                        "name": "用户导入",
                        "parentId": 100,
                        "orderNum": 6
                    },
                    {
                        "id": 1006,
                        "name": "重置密码",
                        "parentId": 100,
                        "orderNum": 7
                    }
                ]
            },
            {
                "id": 101,
                "name": "角色管理",
                "parentId": 1,
                "orderNum": 2,
                "children": [
                    {
                        "id": 1007,
                        "name": "角色查询",
                        "parentId": 101,
                        "orderNum": 1
                    },
                    {
                        "id": 1008,
                        "name": "角色新增",
                        "parentId": 101,
                        "orderNum": 2
                    },
                    {
                        "id": 1009,
                        "name": "角色修改",
                        "parentId": 101,
                        "orderNum": 3
                    },
                    {
                        "id": 1010,
                        "name": "角色删除",
                        "parentId": 101,
                        "orderNum": 4
                    },
                    {
                        "id": 1011,
                        "name": "角色导出",
                        "parentId": 101,
                        "orderNum": 5
                    }
                ]
            },
            {
                "id": 102,
                "name": "菜单管理",
                "parentId": 1,
                "orderNum": 3,
                "children": [
                    {
                        "id": 1012,
                        "name": "菜单查询",
                        "parentId": 102,
                        "orderNum": 1
                    },
                    {
                        "id": 1013,
                        "name": "菜单新增",
                        "parentId": 102,
                        "orderNum": 2
                    },
                    {
                        "id": 1014,
                        "name": "菜单修改",
                        "parentId": 102,
                        "orderNum": 3
                    },
                    {
                        "id": 1015,
                        "name": "菜单删除",
                        "parentId": 102,
                        "orderNum": 4
                    }
                ]
            },
            {
                "id": 103,
                "name": "部门管理",
                "parentId": 1,
                "orderNum": 4,
                "children": [
                    {
                        "id": 1016,
                        "name": "部门查询",
                        "parentId": 103,
                        "orderNum": 1
                    },
                    {
                        "id": 1017,
                        "name": "部门新增",
                        "parentId": 103,
                        "orderNum": 2
                    },
                    {
                        "id": 1018,
                        "name": "部门修改",
                        "parentId": 103,
                        "orderNum": 3
                    },
                    {
                        "id": 1019,
                        "name": "部门删除",
                        "parentId": 103,
                        "orderNum": 4
                    }
                ]
            },
            {
                "id": 104,
                "name": "岗位管理",
                "parentId": 1,
                "orderNum": 5,
                "children": [
                    {
                        "id": 1020,
                        "name": "岗位查询",
                        "parentId": 104,
                        "orderNum": 1
                    },
                    {
                        "id": 1021,
                        "name": "岗位新增",
                        "parentId": 104,
                        "orderNum": 2
                    },
                    {
                        "id": 1022,
                        "name": "岗位修改",
                        "parentId": 104,
                        "orderNum": 3
                    },
                    {
                        "id": 1023,
                        "name": "岗位删除",
                        "parentId": 104,
                        "orderNum": 4
                    },
                    {
                        "id": 1024,
                        "name": "岗位导出",
                        "parentId": 104,
                        "orderNum": 5
                    }
                ]
            },
            {
                "id": 105,
                "name": "字典管理",
                "parentId": 1,
                "orderNum": 6,
                "children": [
                    {
                        "id": 1025,
                        "name": "字典查询",
                        "parentId": 105,
                        "orderNum": 1
                    },
                    {
                        "id": 1026,
                        "name": "字典新增",
                        "parentId": 105,
                        "orderNum": 2
                    },
                    {
                        "id": 1027,
                        "name": "字典修改",
                        "parentId": 105,
                        "orderNum": 3
                    },
                    {
                        "id": 1028,
                        "name": "字典删除",
                        "parentId": 105,
                        "orderNum": 4
                    },
                    {
                        "id": 1029,
                        "name": "字典导出",
                        "parentId": 105,
                        "orderNum": 5
                    }
                ]
            },
            {
                "id": 106,
                "name": "参数设置",
                "parentId": 1,
                "orderNum": 7,
                "children": [
                    {
                        "id": 1030,
                        "name": "参数查询",
                        "parentId": 106,
                        "orderNum": 1
                    },
                    {
                        "id": 1031,
                        "name": "参数新增",
                        "parentId": 106,
                        "orderNum": 2
                    },
                    {
                        "id": 1032,
                        "name": "参数修改",
                        "parentId": 106,
                        "orderNum": 3
                    },
                    {
                        "id": 1033,
                        "name": "参数删除",
                        "parentId": 106,
                        "orderNum": 4
                    },
                    {
                        "id": 1034,
                        "name": "参数导出",
                        "parentId": 106,
                        "orderNum": 5
                    }
                ]
            },
            {
                "id": 107,
                "name": "通知公告",
                "parentId": 1,
                "orderNum": 8,
                "children": [
                    {
                        "id": 1035,
                        "name": "公告查询",
                        "parentId": 107,
                        "orderNum": 1
                    },
                    {
                        "id": 1036,
                        "name": "公告新增",
                        "parentId": 107,
                        "orderNum": 2
                    },
                    {
                        "id": 1037,
                        "name": "公告修改",
                        "parentId": 107,
                        "orderNum": 3
                    },
                    {
                        "id": 1038,
                        "name": "公告删除",
                        "parentId": 107,
                        "orderNum": 4
                    }
                ]
            },
            {
                "id": 108,
                "name": "日志管理",
                "parentId": 1,
                "orderNum": 9,
                "children": [
                    {
                        "id": 500,
                        "name": "操作日志",
                        "parentId": 108,
                        "orderNum": 1,
                        "children": [
                            {
                                "id": 1039,
                                "name": "操作查询",
                                "parentId": 500,
                                "orderNum": 1
                            },
                            {
                                "id": 1040,
                                "name": "操作删除",
                                "parentId": 500,
                                "orderNum": 2
                            },
                            {
                                "id": 1041,
                                "name": "日志导出",
                                "parentId": 500,
                                "orderNum": 3
                            }
                        ]
                    },
                    {
                        "id": 501,
                        "name": "登录日志",
                        "parentId": 108,
                        "orderNum": 2,
                        "children": [
                            {
                                "id": 1042,
                                "name": "登录查询",
                                "parentId": 501,
                                "orderNum": 1
                            },
                            {
                                "id": 1043,
                                "name": "登录删除",
                                "parentId": 501,
                                "orderNum": 2
                            },
                            {
                                "id": 1044,
                                "name": "日志导出",
                                "parentId": 501,
                                "orderNum": 3
                            },
                            {
                                "id": 1045,
                                "name": "账户解锁",
                                "parentId": 501,
                                "orderNum": 4
                            }
                        ]
                    }
                ]
            }
        ]
    },
    {
        "id": 2,
        "name": "系统监控",
        "parentId": 0,
        "orderNum": 2,
        "children": [
            {
                "id": 109,
                "name": "在线用户",
                "parentId": 2,
                "orderNum": 1,
                "children": [
                    {
                        "id": 1046,
                        "name": "在线查询",
                        "parentId": 109,
                        "orderNum": 1
                    },
                    {
                        "id": 1047,
                        "name": "批量强退",
                        "parentId": 109,
                        "orderNum": 2
                    },
                    {
                        "id": 1048,
                        "name": "单条强退",
                        "parentId": 109,
                        "orderNum": 3
                    }
                ]
            },
            {
                "id": 110,
                "name": "定时任务",
                "parentId": 2,
                "orderNum": 2,
                "children": [
                    {
                        "id": 1049,
                        "name": "任务查询",
                        "parentId": 110,
                        "orderNum": 1
                    },
                    {
                        "id": 1050,
                        "name": "任务新增",
                        "parentId": 110,
                        "orderNum": 2
                    },
                    {
                        "id": 1051,
                        "name": "任务修改",
                        "parentId": 110,
                        "orderNum": 3
                    },
                    {
                        "id": 1052,
                        "name": "任务删除",
                        "parentId": 110,
                        "orderNum": 4
                    },
                    {
                        "id": 1053,
                        "name": "状态修改",
                        "parentId": 110,
                        "orderNum": 5
                    },
                    {
                        "id": 1054,
                        "name": "任务导出",
                        "parentId": 110,
                        "orderNum": 6
                    }
                ]
            },
            {
                "id": 111,
                "name": "数据监控",
                "parentId": 2,
                "orderNum": 3
            },
            {
                "id": 112,
                "name": "服务监控",
                "parentId": 2,
                "orderNum": 4
            },
            {
                "id": 113,
                "name": "缓存监控",
                "parentId": 2,
                "orderNum": 5
            },
            {
                "id": 114,
                "name": "缓存列表",
                "parentId": 2,
                "orderNum": 6
            }
        ]
    },
    {
        "id": 3,
        "name": "系统工具",
        "parentId": 0,
        "orderNum": 3,
        "children": [
            {
                "id": 115,
                "name": "表单构建",
                "parentId": 3,
                "orderNum": 1
            },
            {
                "id": 116,
                "name": "代码生成",
                "parentId": 3,
                "orderNum": 2,
                "children": [
                    {
                        "id": 1055,
                        "name": "生成查询",
                        "parentId": 116,
                        "orderNum": 1
                    },
                    {
                        "id": 1056,
                        "name": "生成修改",
                        "parentId": 116,
                        "orderNum": 2
                    },
                    {
                        "id": 1057,
                        "name": "生成删除",
                        "parentId": 116,
                        "orderNum": 3
                    },
                    {
                        "id": 1058,
                        "name": "导入代码",
                        "parentId": 116,
                        "orderNum": 4
                    },
                    {
                        "id": 1059,
                        "name": "预览代码",
                        "parentId": 116,
                        "orderNum": 5
                    },
                    {
                        "id": 1060,
                        "name": "生成代码",
                        "parentId": 116,
                        "orderNum": 6
                    }
                ]
            },
            {
                "id": 117,
                "name": "系统接口",
                "parentId": 3,
                "orderNum": 3
            }
        ]
    }
]

总结

该方式通过非递归方式构建了树结构,但是并非完全取代递归方式构建,只为开发者多提供一种选择。