目录
参考资料:
递归递推的基本理解https://blog.csdn.net/qq_23095607/article/details/125245890
基础知识:
1. 多项并列递归的执行流程
1). 最重要的:
- 多项并列递归的执行流程可以概述为:
传递至底 → 反弹后推
- 画图如下:
2). 解释
- 下面将 结合两个并列的递归对上面所画图进行解释
- ①②③④⑤递归函数传递至底,触发终止条件;
- ⑥⑦消息回弹一级,执行后续所有操作,直到遇到下一个递归函数;
- ⑧⑨⑩11递归函数请求传递至底,触发终止条件;
- 12,13消息回弹一级,执行后续所有操作,直到遇到下一个递归函数;
- 14,15递归函数请求传递至底,触发终止条件;
- 16,17,18,19,20,21消息回弹一级,执行后续操作,无递归函数,则进行返回并后推操作,直到遇到递归函数或者返回到根节点;
- 22,23,24,25递归函数传递至底,触发终止条件;
- 26,27消息回弹一级,执行后续所有操作,直到遇到下一个递归函数;
- 28,29递归函数请求传递至底,触发终止条件;
- 30,31,32,33消息回弹一级,执行后续操作,无递归函数,则进行返回并后推操作,直到遇到递归函数或者返回到根节点;
- 上述模型可以抽象为:
- 按照,并列递归,并列前,并列中,并列后三个位置操作的分类,如图他们的执行顺序为:
- 递归到底,执行并列前所有操作,最末端的仅执行终止条件前操作,按照代码,顺序为,由上至下,由浅入深,如上图中:{1,2,3,4,5},{7,8,9},{11},{15,16,17},{19}
- 最后一次反弹回退,执行并列后所有操作,按照代码,执行顺序为:由上而下,由深而浅,如上图中:{12,13,14},{20,21}
- 当前一个递归函数,及它深层的操作执行完毕后,就会执行并列中的操作。
- 上述模型还可以进一步抽象成
- 进而抽象出执行顺序的概念模型
- 其中,递归至底,指的是最近的未被执行的一个递归函数,直至终止条件
- 反弹后推:反弹指的是触发终止条件,进行返回,后推指的是反弹后执行后面的程序;
简单问题分析
1). 简单程序举例
@Test
public void test_02(){
// 进行n到0 的遍历
serachNum(3);
}
private void serachNum(int num){
// 判断前输出
System.out.println("判断前操作:"+num);
if(num<0){
System.out.println("该数值小于0");
return;
}
//进行递归前操作
System.out.println("递归前操作"+num);
int num_1 = num-1;
serachNum(num_1);
//递归后操作
System.out.println("递归后操作"+num);
}
- 遍历从n到0,例如遍历从3到0,那么这个递归函数的结构可以解析为
- 首先终止条件即为反弹条件,有且只能满足一次,最大值为-1
- 递归函数即为递推到底的缺口
- 累计条件即为每次向下递推的数据更新
那么第一次向下递推到底的输出为:
递归函数前,由上至下执行操作,直到触底
触底后进行反弹,由下至上,执行递归函数的后续操作
那么这个函数的输出为:
判断前操作:3
递归前操作3
判断前操作:2
递归前操作2
判断前操作:1
递归前操作1
判断前操作:0
递归前操作0
判断前操作:-1
该数值小于0
递归后操作0
递归后操作1
递归后操作2
递归后操作3
那么画图可以表示为:
3. 总结
- 递推函数的规律为:
- 通过对自身不断调用,由外而内,直至触底(即有且只满足一次终止条件)
- 然后反弹回上一层,调用后面的方法
并列问题分析:
1. 问题引申
- 通过对以上单项递归进行分析,进而引申出多项递归的分析,以快速排序为例,关于快排的概念,可以参考视频
- 源码(可以不看,看下面的分析)
package com.example.demo.controller;
import java.util.Arrays;
/**
* @program: demoForWeb
* @description: 快排
* @author: wjl
* @create: 2023-02-14 00:47
**/
public class QuickSort {
public static void sort(int array[], int left, int right) {
System.out.println(" 判断前:left:"+left+"======right:"+right+"======array:"+ Arrays.toString(array));
int i, j;
int pivot;
if (left >= right) {
System.out.println(" 判断中:left:" + left + "======right:" + right+"======array:"+ Arrays.toString(array));
return;
}
System.out.println(" 判断后操作前:left:"+left+"======right:"+right+"======array:"+ Arrays.toString(array));
i = left;
j = right;
pivot = array[i];
while (i < j) {
while (i < j && pivot <= array[j])
j--;
if (i < j)
array[i++] = array[j];
while (i < j && pivot > array[i])
i++;
if (i < j)
array[j--] = array[i];
}
array[i] = pivot;
System.out.println(" 操作后迭代前:left:"+left+"======right:"+right+"======array:"+ Arrays.toString(array));
sort(array, left, i - 1);
System.out.println("迭代①后迭代②前:left:"+left+"======right:"+right+"======array:"+ Arrays.toString(array));
sort(array, i + 1, right);
System.out.println(" 迭代②后:left:"+left+"======right:"+right+"======array:"+ Arrays.toString(array));
}
public static void quickSort(int array[]) {
sort(array, 0, array.length - 1);
}
public static void main(String[] args) {
int a[] = { 5, 7, 4, 8, 6, 1 };
quickSort(a);
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
}
}
- 根据上面提到的视频,以及对代码的分析,可以将快排分为三部分
- ①终止条件,即反弹条件,有且只能满足一次,即满足left == right就返回
- ②递推参数的处理,取数组中第一个元素,然后把小于它的元素放于左边,大于它的元素放于右边,如下:
- ③将左半部分进行递归;
- ④将右半部分进行递归;
- ⑤第一次递归前的操作;
- ⑥第一次递归后,第二次递归前的操作;
- ⑦第二次递归后的操作;
2.逻辑图演示
1)基本流程分析
以数组【5,7,4,8,6,1】为例:
- 输入数组[5, 7, 4, 8, 6, 1],以及左右两个下标{left = 0,right = 5}
- 选取下标为0,数值为5做参考,左边小于,右边大于:[1, 4, 5, 8, 6, 7]
- ①入参[1, 4, 5, 8, 6, 7],两个坐标{left = 0,right = 1}
- 选取下标为0,数值为1做参考,左边小于,右边大于:[1, 4, 5, 8, 6, 7]
- ③入参[1, 4, 5, 8, 6, 7],两个坐标{left = 0,right = -1},因为left > right,满足终止条件,所以进入③递归函数后触发反弹后推,到④
- ④入参[1, 4, 5, 8, 6, 7],两个坐标{left = 1,right = 1},因为left = right,满足终止条件,所以进入④递归函数后触发反弹后推,然后执行一系列操作后返回,直到②
- ②入参[1, 4, 5, 8, 6, 7],两个坐标{left = 3,right = 5}
- 选取下标为3,数值为8做参考,左边小于,右边大于:[1, 4, 5, 7, 6, 8]
- ⑤入参[1, 4, 5, 7, 6, 8],两个坐标{left = 3,right = 4}
- 选取下标为3,数值为7做参考,左边小于,右边大于:[1, 4, 5, 6, 7, 8]
- ⑦入参[1, 4, 5, 6, 7, 8],两个坐标{left = 3,right = 3},因为left = right,满足终止条件,所以进入⑦递归函数后触发反弹后推,到⑧
- ⑧入参[1, 4, 5, 6, 7, 8],两个坐标{left = 5,right = 4},因为left > right,满足终止条件,所以进入⑧递推函数后触发反弹后推,然后执行一系列操作后返回,直到⑥
- ⑥入参[1, 4, 5, 6, 7, 8],两个坐标{left = 6,right = 5},因为left>right,满足终止条件,所以进入⑥递归函数后触发反弹后推,然后执行一系列操作后,直到终点;
- 这就是整个分析过程。
2)分析注意事项
- 对于上述,入参以及坐标建议不要深究(深究可以看代码),这里仅仅是为了说明递归多并列项,各个部分的执行顺序
- 下面,你只需要注意:上图中箭头流程,各个递推函数入参,以及各个部分的输出语句,来进行多重并列递归的执行顺序
A. 首先在①之前,输出为:
判断前:left:0======right:5======array:[5, 7, 4, 8, 6, 1]
判断后操作前:left:0======right:5======array:[5, 7, 4, 8, 6, 1]
操作后迭代前:left:0======right:5======array:[1, 4, 5, 8, 6, 7]
B.进入①到达③之前,输出为:
判断前:left:0======right:1======array:[1, 4, 5, 8, 6, 7]
判断后操作前:left:0======right:1======array:[1, 4, 5, 8, 6, 7]
操作后迭代前:left:0======right:1======array:[1, 4, 5, 8, 6, 7]
C. 进入③,反弹前,输出为:
判断前:left:0======right:-1======array:[1, 4, 5, 8, 6, 7]
判断中:left:0======right:-1======array:[1, 4, 5, 8, 6, 7]
D. 反弹后,进入④前,输出为:
迭代①后迭代②前:left:0======right:1======array:[1, 4, 5, 8, 6, 7]
E. 进入④,反弹前,输出为:
判断前:left:1======right:1======array:[1, 4, 5, 8, 6, 7]
判断中:left:1======right:1======array:[1, 4, 5, 8, 6, 7]
F. 反弹后,进入②前,输出为:
迭代②后:left:0======right:1======array:[1, 4, 5, 8, 6, 7]
迭代①后迭代②前:left:0======right:5======array:[1, 4, 5, 8, 6, 7]
G. 以此类推,后续结果为:
②~⑤
判断前:left:3======right:5======array:[1, 4, 5, 8, 6, 7]
判断后操作前:left:3======right:5======array:[1, 4, 5, 8, 6, 7]
操作后迭代前:left:3======right:5======array:[1, 4, 5, 7, 6, 8]⑤~⑦
判断前:left:3======right:4======array:[1, 4, 5, 7, 6, 8]
判断后操作前:left:3======right:4======array:[1, 4, 5, 7, 6, 8]
操作后迭代前:left:3======right:4======array:[1, 4, 5, 6, 7, 8]⑦~反弹
判断前:left:3======right:3======array:[1, 4, 5, 6, 7, 8]
判断中:left:3======right:3======array:[1, 4, 5, 6, 7, 8]反弹~⑧
迭代①后迭代②前:left:3======right:4======array:[1, 4, 5, 6, 7, 8]⑧~反弹
判断前:left:5======right:4======array:[1, 4, 5, 6, 7, 8]
判断中:left:5======right:4======array:[1, 4, 5, 6, 7, 8]反弹~⑥
迭代②后:left:3======right:4======array:[1, 4, 5, 6, 7, 8]
迭代①后迭代②前:left:3======right:5======array:[1, 4, 5, 6, 7, 8]⑥~反弹
判断前:left:6======right:5======array:[1, 4, 5, 6, 7, 8]
判断中:left:6======right:5======array:[1, 4, 5, 6, 7, 8]反弹~结束
迭代②后:left:3======right:5======array:[1, 4, 5, 6, 7, 8]
迭代②后:left:0======right:5======array:[1, 4, 5, 6, 7, 8]
3. 上述分析总结(理论篇)
-
所以,递归、递推函数可以的比喻为一个小球,自右向左,经过一个满是孔洞的地面;
-
最上面孔洞最大,孔洞的大小随孔洞的深度递减;
-
大孔洞下面还有小孔洞;
-
触底反弹到离它最近的下一个孔洞,直至至最高层;
- 那么这个过程可以概括为:递推至底、反弹后推的不断循环
- 通过对自身不断地调用,将数据推至边界条件,直至返回;
- 返回后又去寻找下一个自身的方法,继续推至边界条件,如此循环往复,直至遍历完所有;
- 在努力寻找自身方法的路途中,顺便执行了路途中所有的数据操作;
4. 一些特别的处理方式
- 首先是数据的累计递归函数,高中数学不好的建议学习通项公式,以及重点特征根法
- 下面以斐波那契数列为例,来说明累计式递归函数,对于任一值的结论,如果对斐波那契数列还不熟,或者斐波那契数列的java代码可以参考
- 斐波那契数列的递推式为:
F(n)=F(n -1)+ F(n -2)(n ≥ 2,n ∈ N*) ;
F(1)= 1;
F(0)=1
- 那么它的通项公式为:(见百度百科)
- 第二种就是非累计式的,各个数据操作的执行情况 ,以上述快排为例
- 我们可以把,递归、递推函数分为判断前,判断,判断后,递归n后,这几部分
- 那么我们就可以按照输入值【5,7,4,8,6,1】 ,写出并关系的递归公式
- 我们通过各个项式的累加,就可以得到各个阶段的数据操作;
综合总结(实践篇):
1. n项递归、递推函数的读取/理解
- 递归、递推函数的读其实是写的逆向运算,写的方法见文章
- 首先明确终止条件,数据操作部分,递归递推部分(注意:终止条件在每一次递推到底过程中有且只满足一次)
- 然后写出他们的递推公式F(),公式中的部分可以是数学关系(加减乘除等)或这是与或非关系
- 根据公式就能看出递归、递推方法的含义;
2. 递归、递推函数理解举例(数学关系)
- 数学关系递归函数举例:斐波那契数列java
@Test
public void A(){
System.out.println("前100个斐波那契数列和为:" + function(100));
}
public int function(int num){
if(num == 1||num == 2){
return 1;
}else if(num > 2){
return function(num-1)+function(num-2);
}else{
System.out.println("error");
return 0;
}
}
- 结构分析
- 所以我们根据终止条件和递归条件,得到下面关系式
F(n)=F(n -1)+ F(n -2)(n ≥ 2,n ∈ N*) ;
F(2)= 1;
F(1)=1
- 然后我们根据关系式的解读为:
数n对某方法的结果等于数n-1对该方法的结果与数n-2对该方法的结果
且
数2,数1对该方法的结果为1
3.递归、递推函数理解举例(非数学关系:&|!)
- 快速排序为例进行说明
- 代码见上,结构分析如下(仅考虑有效的数据操作)
- 其中数据操作的意思为: 取数组中第一个元素,然后把小于它的元素放于左边,大于它的元素放于右边,如下:
- 那么这个关系式可以写为:
F(array,left,right) = OP1 & F(array,left,i-1) & F(array,i+1,right)
终止条件:left>=right表示当左下标和右下标在同一个格子中,甚至更过分时,就会触发返回条件
因为终止条件,即递推到底条件有且只满足一次
- 根据这个关系式,得到的结论为
输入一个数组,经过OP1(以该数组左下标对应的数值为参考,将比该数值小的置于左边,比该数值大的置于右边,参考数值下标为i),然后将i左边的数据在进行一次该操作,i右边的数据再进行一次操作,直到数据只剩一个甚至没有