递归算法使用

简介: 通常递归算法可以将一个问题的重复调用进行分解,将其分解成一个多次调用,最终完成筛选或者需要的数据。比如我们常见的斐波那契数列就是这样的:0、1、1、2、3、5、8、13、21、34这样的递归数据,可以通过此来总结出它的数学公式规律:F(n) = F(n-1) + F(n-2)的这个过程就是是借助上面的F(0)和F(1)的结果来进行递推的,这个过程在数学上是一个数列的递推公式,在高中我们学过的数列上。我还记得当时求解递推公式可以使用函数方程,而函数方程的思想想想其实是借助了微分方程逆推得到积分方程的过程,或者说是采用不动点法可以实现这一求解的过程。这个过程,在我看到高等数学的时候才明白,现在想

1.什么是递归算法

通常递归算法可以将一个问题的重复调用进行分解,将其分解成一个多次调用,最终完成筛选或者需要的数据。比如我们常见的斐波那契数列就是这样的:

0、1、1、2、3、5、8、13、21、34这样的递归数据,可以通过此来总结出它的数学公式规律:

F(0) =0;
F(1) =1;
F(n) =F(n-1) +F(n-2);(n>=2)

将其转成程序可以写成:

publicstaticintgetFn(intn){
//先排除不满足的条件,然后针对特定场景的条件,最后是共性的实现if(n<0){
return-1;
  }  
if(n==0){
F(0) =0;
  }
if(n==1){
F(1) =1;
  }
if(n>1){
F(n) =F(n-1) +F(n-2); 
 }
}

而F(n) = F(n-1) + F(n-2)的这个过程就是是借助上面的F(0)和F(1)的结果来进行递推的,这个过程在数学上是一个数列的递推公式,在高中我们学过的数列上。我还记得当时求解递推公式可以使用函数方程,而函数方程的思想想想其实是借助了微分方程逆推得到积分方程的过程,或者说是采用不动点法可以实现这一求解的过程。这个过程,在我看到高等数学的时候才明白,现在想来确实很想笑,^_^….

而这个过程是重复的,因此我们可以提取出共性的部分,借助共性的部分进行重复,即可得到我们想要的结果。

2.项目中使用递归

而在我们的项目中,经常会出现像树形菜单的需求。比如我们想将权限做成按钮级别,这个时候就需要做一个树形菜单,可以让用户根据需要进行启用和禁用。而树形菜单由于有父子级别,因此我们如果需要将菜单返回给前端,通常是需要将其组装成树形结构的。下面用自己去年做的一个需求来说明吧,需求是做一个医疗文书的需求,涉及到文件的上传,同时将文件的PDF、PPT、word转成图片,这里省略掉了文件上传和文件转换操作,只涉及树形递归操作。菜单树的效果是可以实现打印,同时可以进行新增子项,可以进行启用和禁用操作。此时的启用和禁用涉及到父子关系的操作。

微信图片_20221214033131.png

树形菜单目录

此时涉及到一个流的操作和用户可以根据需要可以自己去订阅自己的字典目录,进行文件的传输。也即文件流操作和树形结构以及pdf、ppt、word和图片的转换。

在此之前,我们可以将字典的名称放在一个顶尖目录为0的目录上,然后将字典的名称展示出来,也即是一级目录,此时的目录以层次进行划分,而一级目录是在字典目录中新增的。这样我们就可以进行我们的递归操作了。

如果需要进行递归,此时我们首先需要进行设计事先在文件目录中涉及一个顶尖目录,它是以0开头的。然后后面的都是可以依次为基础的。

树的数据结构:当时写的时候借鉴了网上的资料进行了自己的数据组装

publicclassMenuTree {
privateList<HdMedicalDocPO>menuList;
publicMenuTree(List<HdMedicalDocPO>menuList) {
this.menuList=menuList;
    }
// 建立树形结构publicList<HdMedicalDocPO>builTree() {
List<HdMedicalDocPO>treeMenus=newArrayList<>();
for (HdMedicalDocPOmenuNode : getRootNode()) {
menuNode=buildChilTree(menuNode);
treeMenus.add(menuNode);
        }
returntreeMenus;
    }
// 递归,建立子树形结构privateHdMedicalDocPObuildChilTree(HdMedicalDocPOpNode) {
List<HdMedicalDocPO>chilMenus=newArrayList<>();
for (HdMedicalDocPOmenuNode : menuList) {
if (menuNode.getPDocCode().equals(pNode.getDocCode())) {
chilMenus.add(buildChilTree(menuNode));
            }
        }
pNode.setChildren(chilMenus);
returnpNode;
    }
// 获取根节点privateList<HdMedicalDocPO>getRootNode() {
List<HdMedicalDocPO>rootMenuLists=newArrayList<>();
for (HdMedicalDocPOmenuNode : menuList) {
if (menuNode.getPDocCode().equals("0")) {
rootMenuLists.add(menuNode);
            }
        }
returnrootMenuLists;
    }
}

进行业务处理:

/*** @description 获取患者医疗文书树形结构* @author lyz* @date 2020/6/24 19:21* @param isEnable**/@RequestMapping("getTreeData")
@ApiOperation(httpMethod="POST", value="获取患者医疗文书树形结构")
publicHttpResult<List<HdMedicalDocPO>>getTreeData(@ApiParam(name="isEnable") @RequestParam(required=false) BooleanisEnable) {
HttpResult<List<HdMedicalDocPO>>result=newHttpResult<>();
IntegerfkTenantId=UserUtils.getTenantId();
List<HdMedicalDocPO>list=hdMedicalDocService.listByEnableFkId(isEnable, fkTenantId);
List<HdMedicalDocPO>medicalDocPOList;
// 说明此时会进行禁用操作筛选,拿到与之相关的禁用父子菜单,然后进行树的构建if (Objects.nonNull(isEnable) &&Objects.equals(isEnable, false)) {
// 找出所有的节点信息至顶级medicalDocPOList=builSelectTree(list);
// 构建菜单树MenuTreemenuTree=newMenuTree(medicalDocPOList);
list=menuTree.builTree();
    } else {
MenuTreemenuTree=newMenuTree(list);
list=menuTree.builTree();
    }
result.setRs(list);
returnresult;
}

进行菜单式的构建:

/*** @description 进行禁用条件筛选, 这里先筛选处理所有与禁用相关的父子操作,然后进行构建* @author lyz* @date 2020/6/24 10:12* @param menuList**/publicList<HdMedicalDocPO>builSelectTree(List<HdMedicalDocPO>menuList) {
// 拿到所在租户所有的菜单List<HdMedicalDoc>listAll=hdMedicalDocService.listByFkTenantId(UserUtils.getTenantId());
List<HdMedicalDocPO>hdMedicalDocPOList=newArrayList<>();
for (HdMedicalDocPOmenuNode : menuList) {
// 进行递归操作,将数据放入到数据中recursiveTree(hdMedicalDocPOList, menuNode, listAll);
hdMedicalDocPOList.add(menuNode);
    }
hdMedicalDocPOList=hdMedicalDocPOList.stream().collect(Collectors        .collectingAndThen(Collectors.toCollection(() ->newTreeSet<>(Comparator.comparing(HdMedicalDocPO::getDocCode))), ArrayList::new));
returnhdMedicalDocPOList;
}

基于菜单树进行树的递归操作:

/*** @description 递归菜单树* @author lyz* @date 2020/6/24 10:10* @param hdMedicalDocPOList* @param menuNode* @return void**/privatevoidrecursiveTree(List<HdMedicalDocPO>hdMedicalDocPOList, HdMedicalDocPOmenuNode, List<HdMedicalDoc>listAll) {
if (!Objects.equals(menuNode.getPDocCode(), "0")) {
hdMedicalDocPOList.add(menuNode);
HdMedicalDocmedicalDoc=null;
// 进行匹配for (HdMedicalDochdMedicalDoc : listAll) {
if (hdMedicalDoc.getDocCode().equals(menuNode.getPDocCode())) {
medicalDoc=hdMedicalDoc;
            }
        }
// 将父菜单设置为当前菜单,并进行进一步拿到顶级菜单HdMedicalDocPOmedicalDocPO=BeanUtils.copyProperties(medicalDoc, HdMedicalDocPO.class);
recursiveTree(hdMedicalDocPOList, medicalDocPO, listAll);
    } else {
// 到了顶级,终止一次递归操作hdMedicalDocPOList.add(menuNode);
    }
}

这是自己当时写好的业务和递归,文件上传和文件转换、打印就不在这里展示了,以上就是分享给大家的。

3.遇到的问题和解决

想写好递归,首先想好需要写的关系,这样递归就可以很好的写出来了。在这过程中,我也遇到过,当时文件上传的时候,我在本地测试上传的时候,没有什么问题。但是当时测试安装的软件是wps的,而我安装的是office的软件。他测试的时候,给我提了一个bug。在他的系统没有出现问题,当时我用了一个jacob的jar包,因此当时也是因为使用这个包的原因,所以在测试的过程中和测试配合发现,当时的jacob包在我调用PDF转图片的时候,会使用jacob调用offcie中的active中的函数,因此会出现wps出现问题的情况。同时在这个过程中,也发现一些坑,就是当时使用的poi在3.13版本的时候,不支持图片、pdf的正常转换。当时将poi升级成了3.14之后,发现支持。当时还因为升级poi怕影响excel的导入导出操作,还做了一些测试和检查。同时也说明了一个问题,就是如果软件升级的时候,还是最好使用一些比较新和稳定的版本,这样一些已知的bug被修复,一些功能可以正常使用。

4.总结

什么时候该使用递归,遇到的问题是重复性操作,同时有终止的条件,可以进行递推,此时就可以考虑。同时这个问题可以进行分解。递归的使用还是很广泛的,比如机器学习中,经常基于一个公式进行递推。比如常用的菜单树,都是可以使用递归的。

目录
相关文章
|
23天前
|
存储
非递归实现后序遍历的空间复杂度是多少?
综上所述,非递归实现后序遍历的空间复杂度在不同实现方式下有所不同,但总体上与二叉树的高度相关,最坏情况下为 $O(n)$,最好情况下为 $O(\log_2 n)$。
|
2月前
|
存储 算法
递归算法
【10月更文挑战第11天】递归算法是一种强大而又具有挑战性的算法技术。通过深入理解和掌握递归的原理、应用以及优化方法,我们可以更好地利用它来解决各种问题,并在编程实践中发挥其独特的优势。同时,我们也要注意递归算法可能带来的性能和栈空间问题,通过合理的设计和优化来提高算法的效率和稳定性。
42 1
|
7月前
|
算法
递归算法实现二分查找
本文简要介绍了递归实现的二分查找算法,这是一种在有序列表中快速查找的策略。递归方法虽在实际应用中较少,但有助于理解递归思想,为学习数据结构中的树内容打下基础。文中提供了原版和递归版本的二分查找代码,并强调了递归算法中处理未找到情况的注意事项。此外,还提到了递归在解决复杂问题时的优势,并通过链接分享了一个关于递归实现素数判断的例子。
101 2
|
机器学习/深度学习 人工智能 算法
深入理解递归算法
概述 定义 计算机科学中,递归是一种解决计算问题的方法,其中解决方案取决于同一类问题的更小子集 In computer science, recursion is a method of solving a computational problem where the solution depends on solutions to smaller instances of the same problem. 比如单链表递归遍历的例子: void f(Node node) { if(node == null) { return; } print
71 0
|
7月前
|
算法
递归算法练习
递归算法练习
36 0
递归和非递归分别实现求第n个斐波那契数
递归和非递归分别实现求第n个斐波那契数
65 0
非递归实现二叉树遍历
非递归实现二叉树遍历
52 0
|
存储 算法 JavaScript
算法系列-二叉树遍历(非递归实现)
在内卷潮流的席卷下,身为算法小白的我不得不问自己,是否得踏上征程,征服这座巍巍高山。 从零开始,终点不知何方,取决于自己可以坚持多久。 希望你可以和我一样,克服恐惧,哪怕毫无基础,哪怕天生愚钝,依然选择直面困难。
汉诺塔(递归+ 非递归版)
汉诺塔问题(又称为河内塔问题),是一个大家熟知的问题。在A,B,C三根柱子上, 有n个不同大小的圆盘(假设半径分别为1-n吧),一开始他们都叠在我A上(如图所示),你的目标是在最少的合法移动步数内将所有盘子从A塔移动到C塔。 游戏中的每一步规则如下:
246 1
汉诺塔(递归+ 非递归版)
|
机器学习/深度学习 算法
数据结构与算法—递归算法(从阶乘、斐波那契到汉诺塔的递归图解)
递归:就是函数自己调用自己。 子问题须与原始问题为同样的事,或者更为简单; 递归通常可以简单的处理子问题,但是不一定是最好的。
139 0
数据结构与算法—递归算法(从阶乘、斐波那契到汉诺塔的递归图解)