InkSoul/content/algorithm/递归.md

151 lines
5.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

---
title: "递归"
date: 2022-05-10T09:10:48+08:00
---
* 递归技术
* 直接或间接地调用自身的算法称为递归算法
* 用函数自身给出定义的函数称为递归函数
* 每个递归函数都必须有非递归定义的初始值,否则递归函数无法计算
---------------------
* 阶乘函数
* 可递归地定义为$n!= \begin{cases}1,& \text{n=0} \\\\ n(n-1)!,&\text{n>0} \end{cases}$
```java
public static int Factorial(int n){
if(n==0)
return 1;
return n*Factorial(n-1);
}
-----------------------
* Fibonacci数列
* 可递归地定义为$F(n)=\begin{cases}1, &\text{n=0},1 \\\\ F(n-1)+F(n-2) &\text{n>1} \end{cases}$
```java
public static int fibonacci(int n){
if(n<=1)
return 1;
return fibonacci(n-1)+fibonacci(n-2);
}
----------------------------
* 排列问题
* 当$n=1时perm(R)=(r)$,其中r是集合R中唯一的元素
* $当n>1时,perm(R)=(r)$
$perm(R)由(r_1)perm(R_1),(r_2)perm(R_2),\cdots,(r_n)perm(R_n)构成$
* 算法perm(list,k,m)递归地产生所有前缀是list[0:k-1],且后缀是list[k:m]的全排列的所有排列。调用perm(list,0,n-1)即产生list[0:n-1]的全排列
* 一般情况下,$k<m$
```java
public static void perm(Object[] list,int k,int m){
if(k==m){
//只剩一个元素
for(int i=0;i<=m;i++)
System.out.print(list[i]);
System.out.println();
}
else
//还有多个元素,递归产生排列
for(int i=k;i<=m;i++)
{
MyMath.swap(list,k,i);
perm(list,k+1,m);
MyMath.swap(list,k,i);
}
}
public static class MyMath{
public static void swap(Object[] list,int k,int m){
int temp = k;
k = m;
m = temp;
}
}
--------------------
* 整数划分问题
* 将正整数$n$表示成一系列正整数之和,$n=n_1+n_2+...+n_k$,其中$n_1\geq n_2\geq ...\geq n_k\geq 1,k\geq 1$
* 将最大加数$n_1$不大于$m$的划分个数记作$q(n,m)$可建立如下递归关系
* 当最大加数$n_1$不大于1时,任何正整数n只有一种划分形式,即$n=\begin{matrix} n \\\\ \overbrace{1+1+\cdots+1}\end{matrix}$
* 最大加数$n_1$实际上不能大于$n$。因此,$q(1,m)=1$。
* 正整数$n$的划分由$n_1=n$的划分和$n_1\leq n-1$的划分组成
* 正整数$n$的最大加数$n_1$不大于$m$的划分由$n_1=m$的划分和$n_1\leq m-1$的划分组成
```java
public static int q(int n,int m){
if((n<1)||(m<1))
return 0;
if((n==1)||(m==1))
return 1;
if(n<m)
return q(n,n);
if(n==m)
return q(n,m-1)+1;
return q(n,m-1)+q(n-m,m);
}
---------------
* Hanoi塔问题
* a,b,c是三个塔座。开始是在塔座a上有一叠共n个圆盘,这些圆盘自下而上,由大到小地叠在一起。各圆盘从小到大编号为12,···,n。现要求将塔座a上的这一叠圆盘移到塔座b上,并仍按同样顺序叠置。在移动圆盘时应该遵守以下移动规则。
* 每次只移动一个圆盘
* 任何时刻都不允许将较大的圆盘压在较小的圆盘之上。
* 在满足前两个规则的前提下,可将圆盘移至abc任一塔座上
* 递归关系
* $n=1$时将编号为一的圆盘从塔座a直接移至塔座b上即可。
* $n>1$时需要利用塔座c作为辅助塔座
* 将$n-1$个较小的圆盘依照移动规则从塔座a移至塔座c
* 将剩下的最大圆盘从塔座a移至塔座b
* 将$n-1$个较小的圆盘依照移动规则从塔座c移至塔座b
```java
public static void hanoi(int n,char a,char b,char c){
if(n>0){
hanoi(n-1, a, c, b);
move(n,a,b);
hanoi(n-1,c,b,a);
}
}
public static void move(int n,char a,char b){
System.out.println("第"+n+"个盘子从"+a+"--->"+b);
}
```
* 递归调用总结和系统原理
* 实现递归调用的关键:为算法建立递归调用工作栈
* 运行被调用算法前的行为
* 为所有实参指针,返回地址等信息传递给被调用算法
* 为被调用算法的局部变量分配存储区
* 将控制转移到被调用算法的入口
* 从被调用算法返回调用算法时
* 保存被调用算法的计算结果
* 释放分配给被调用算法的数据区
* 依照被调用算法保存的返回地址将控制转移到调用算法
* 嵌套调用时的系统原则:后调用先返回,即算法间的信息传递和控制转移通过栈来实现
* 递归算法的调用层次
* 调用一个递归算法的主算法为第0层算法
* 从主算法调用递归算法为进入第1层调用
* 从第i层递归调用本算法为进入第i+1层调用。
* 退出第i层递归调用则返回至i-1层调用。
* 递归调用的栈使用情况示意
| 主算法栈块 |
| :---------------------------: |
| M |
| 主算法调用递归算法A的栈块 |
| 算法A的第一层递归调用工作记录 |
| 算法A的第二层递归调用工作记录 |
| TOP |
| M |