记一次使用递归进行功能开发场景关于递归编码

场景

笔者有一张分类表,表结构如下

-- 分类
drop table if exists `category`;
create table `category` (
    `id` bigint not null comment 'id',
    `parent` bigint not null default 0 comment '父id',
    `name` varchar(50) not null comment '书名',
    `sort` int comment '顺序',
    primary key (`id`)
) engine=innodb default charset=utf8mb4 comment='父类表';

insert into `category` (id, parent, name, sort) values (100, 0, '前端', 100);
insert into `category` (id, parent, name, sort) values (101, 100, 'Vue', 101);
insert into `category` (id, parent, name, sort) values (102, 100, 'HTML & CSS', 102);
insert into `category` (id, parent, name, sort) values (200, 0, '后端', 200);
insert into `category` (id, parent, name, sort) values (201, 200, 'java', 201);
insert into `category` (id, parent, name, sort) values (202, 200, 'spring', 202);
insert into `category` (id, parent, name, sort) values (203, 200, '并发', 203);
insert into `category` (id, parent, name, sort) values (204, 200, '数据结构', 204);
复制代码

不难看出这是一张分类管理表,parent代表当前数据的父节点,若为0则代表是根节点。为了组装出一科树笔者思考再三选用递归进行组装。

关于递归

优点

代码更简洁清晰,可读性更好

缺点

时间和空间消耗比较大。每一次函数调用都需要在内存栈中分配空间以保存参数,返回地址以及临时变量,而且往栈里面压入数据和弹出都需要时间。

另外递归会有重复的计算。递归本质是把一个问题分解为多个问题,如果这多个问题存在重复计算,有时候会随着n成指数增长。斐波那契的递归就是一个例子。

递归还有栈溢出的问题,每个进程的栈容量是有限的。由于递归需要系统堆栈,所以空间消耗要比非递归代码要大很多。而且,如果递归深度太大,可能系统撑不住。

编码

从数据库中查出数据

List<Category> categoryList = categoryMapper.selectByExample(categoryExample);
复制代码

有了数据,开始分析递归算法思路

确定递归函数的功能

返回当前节点的子节点集合,例如当前节点id为100,则返回parent为100的节点的集合。

确定终止条件

这个递归函数会进行一次for循环,循环结束就代表遍历结束。

寻找重复的工作

从根节点开始,用自己的id去集合中找自己的子节点集合。查找到对应子节点,子节点也需要查找其所对应的子节点,所以也需要用自己id去集合中找自己的子节点,如此往复。

编写函数

注意这里笔者比较long类型的方式,说白就是Long类型-128, 127之间是从缓存中取出的,所以这个范围比较是没有问题,但是在这个范围以外会new对象,使用==比较的是地址肯定会返回false。具体原因可参考这篇博客
[# Java中判断两个Long类型是否相等](www.cnblogs.com/shenwen/p/1…)

  public List<Category> list(CategoryReq req) {
        logger.info("开始获取所有分类列表,请求参数{}", JSONUtil.toJsonStr(req));
        CategoryExample categoryExample = new CategoryExample();
//        List<Category> categoryList = categoryMapper.selectCategoryTree();
        List<Category> categoryList = categoryMapper.selectByExample(categoryExample);
        List<Category> categoryTree = getCategoryTree(categoryList);
//        List<CategoryResp> respList=CopyUtil.copyList(categoryList, CategoryResp.class);
        return categoryTree;
    }

    /**
     * 使用递归完成树的组装
     *
     * @param categoryList
     * @return
     */
    private List<Category> getCategoryTree(List<Category> categoryList) {
        List<Category> resultList = new ArrayList<>();
        for (int i = 0; i < categoryList.size(); i++) {
            Category category = categoryList.get(i);
//            若是根节点,则存入集合中 并开始找寻子节点
            if (category.getParent().longValue() == 0L) {
//                初始化集合成员变量
                category.setChildren(new ArrayList<>());
//                递归的开始,将查找的子节点集合存到自己的集合中
                category.getChildren().addAll(getCategoryTree(categoryList, category.getId()));
                resultList.add(category);
            }
        }
        return resultList;

    }


    private List<Category> getCategoryTree(List<Category> categoryList, Long id) {
        List<Category> resultList = new ArrayList<>();
        for (int i = 0; i < categoryList.size(); i++) {
            Category category = categoryList.get(i);
            if (category.getParent().longValue() == id.longValue()) {
                resultList.add(category);
                category.setChildren(new ArrayList<>());
//                递归查找子节点对应的孙节点
                category.getChildren().addAll(getCategoryTree(categoryList, category.getId()));
            }
        }

        return resultList;
    }
复制代码