递归(带例题)

1.什么是递归

一个函数调用它本身就是递归。递归通常把一个大型复杂的问题层层转化为子问题,直到到子问题无需进一步递归就可以解决的地步。递归极大地降低了代码量。

通常来讲一个递归算法由以下部分组成:

  • 能够不使用递归来产生答案的终止方案
  • 可将所有其他情况拆分到基本案例

2.递归的作用

递归算法在以下情况下有着很大的作用:

  • 数据的定义是按递归定义的
  • 问题解法按递归算法实现
  • 数据的结构形式是按递归定义的

3.递归的应用

以上文中递归适用的三种情况来帮助大家对递归有一个更好的理解


3.1 当数据的定义是按递归定义的时

最常见的就是Fibonacci数列

F0 = 0 ; F1 = 1; Fn = Fn-1 + Fn-2 , n ≥ \geq 2

比如说我们现在要求出Fibonacci数列的第5个数字,根据上文提到的递归的组成部分,我们将递归函数的组成分为两部分:

  • 当传入的实参是0时返回0,当传入的实参时1时返回1(终止方案)
  • 其他情况函数递归调用自己(拆分)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-flwOuEed-1644237481659)(C:\Users\lemer\Desktop\digui.png)]

递归算法会将f(5)逐步拆分成f(1) + f(0) + f(1) + f(1) + f(0) + f(1) + f(0) + f(1)

写成程序是这样的:

#include<iostream>
using namespace std;

int Fibonacci(int n) {
	if (n == 0)	//终止方案
		return 0;  
	if (n == 1)	//终止方案
		return 1;  
	return Fibonacci(n - 1) + Fibonacci(n - 2); //拆分
}

int main() {
	cout << Fibonacci(5);
	return 0;
}

3.2 问题解法按递归算法实现时

思考下面的问题:把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放, 问共有多少种不同的分法?(5,1,1和1,5,1 是同一种分法。)

问题分析:

1、确定参数:题目有效数据只有苹果和盘子,在构造递归函数时不妨设m个苹果放在n个盘子里放法总数是f(i, j)

2、终止方案:(1)当没有苹果时所有盘子为空,返回1;(2)当没有盘子时返回0

3、拆分:(1)当苹果数m小于盘子数n时必定有盘子为空,且空盘子数至少为n - m,此情况的结果等同与将m个苹果放入m个盘子;(2)当苹果数m大于等于盘子数n时分两种情况:有盘子空和没有盘子空。表示有盘子空可以在递归时将盘子的数量减1但苹果数不变;表示没有盘子空可以先将每个盘子里预放一个苹果,再在递归调用函数时盘子数量不变,但是苹果数量变成原苹果数量减盘子数量。

写成程序是这样的:

#include<iostream>
using namespace std;

int f(int m, int n) {
	if (m == 0) //终止方案
		return 1;
	if (n == 0) //终止方案
		return 0;
	if (n > m) //拆分
		return f(m, m);
	return f(m, n - 1) + f(m - n, n); //拆分
}

int main() {
	int m, n;
	cin >> m >> n;
	cout << f(m, n) << endl;	
	return 0;
}

3.3 数据的结构形式是按递归定义的时

如二叉树的遍历、广度优先搜索等问题

现在给定一个二叉树,要求找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

问题分析:

(1)终止条件:遍历到的节点是空节点

(2)拆分:向当前节点的左子树和右子树遍历,每遍历一次深度加一,每次返回值取其左子树和右子树的最大深度值

#include<iostream>
using namespace std;

typedef struct BTree
{
    int    value;
    struct BTree* lchild;
    struct BTree* rchild;
}BTree;

BTree* CreateBTree(BTree* node, int* num, int& index)
{
    if (num[index] == 0)
        return NULL;
    else
    {
        node = new BTree;
        node->value = num[index];
        node->lchild = CreateBTree(node->lchild, num, ++index);
        node->rchild = CreateBTree(node->rchild, num, ++index);
    }
    return node;
}
void preOrder(BTree* root)
{
    if (root == NULL)
        return;
    cout << root->value << " ";
    preOrder(root->lchild);       //递归 左子树
    preOrder(root->rchild);       //递归 右子树   
}
int getdepth(BTree* root)
{
    if (root == NULL) //终止条件
        return 0;
    int lchild_depth = getdepth(root->lchild);
    int rchild_depth = getdepth(root->rchild);
    return max(lchild_depth, rchild_depth) + 1; //拆分
}
int main()
{
    int num[] = { 1,2,4,8,0,0,9,0,0,5,10,0,0,11,0,0,3,6,12,0,0,13,0,0,7,14,0,0,15,0,0 };
    BTree* root = NULL;
    int index = 0;
    root = CreateBTree(root, num, index);
    cout << "前序遍历为:\n";
    preOrder(root);
    cout << endl;
    cout << "最大深度为: ";
    cout << getdepth(root);
    return 0;
}

4.递归的优缺点及其优化

4.1 优点

在一些特定问题中递归比循环更简洁,递归像一个黑盒子,编写时只需要关注边界条件和拆分公式

4.2 缺点

(1)递归是对函数的调用,需要占用栈空间,时间和空间消耗较大

(2)如果不对递归函数做一定的记忆化处理的话会有很多的重复运算,浪费时间和空间

(3)调用栈可能会溢出

4.3 优化(记忆化)

我们看到3.1的例子代码,当n取一个很大的值时会让程序做很多次重复的计算,我们这时可以采用空间换取时间的思想,用一个表记录算出来过的值,每次运算时先在表里找该值有没有被算过,如果有直接用之前算出来的值,如果没有就算出来后将其存入表中

这时代码实现:

class Solution {
public:
    unordered_map<int, int>hash;  //空间换时间
    int fib(int n) {
        if(n == 0) return 0;
        if(n == 1) return 1;
        if(hash[n]) return hash[n];  //算之前查找有没有被算出来过
        return hash[n] = fib(n - 1) + fib(n - 2); //没有的话把算出来的值存起来
    }
};

5.总结

看到了这里的话相信大家对递归有了一个更好的理解,这里有两个题目可以帮助加深大家对递归的理解:

力扣 70 爬楼梯;力扣 104 二叉树的最大深度

今天的分享就到这里,我们下期再见!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: 一个经典的Python递归算法例题是计算阶乘。通过递归的方式,可以简洁地实现阶乘的计算。例如,可以使用以下代码来计算阶乘: ```python def factorial(n): if n == 1: return n return n * factorial(n-1) res = factorial(5) print(res) ``` 在这个例子中,我们定义了一个名为`factorial`的函数,它接受一个参数`n`,表示要求的数的阶乘。如果`n`等于1,那么阶乘的结果就是1,直接返回1。否则,我们将`n`乘以`factorial(n-1)`,也就是`n`乘以比它小1的数的阶乘。最后,我们调用`factorial(5)`来计算5的阶乘,并将结果赋给`res`变量,然后打印出结果。 这个例子展示了递归算法的简洁性和效率。通过递归,我们可以将复杂的问题简化为更小的子问题,并通过不断调用自身来解决这些子问题,最终得到整个问题的解答。递归算法在解决一些数学问题和数据结构相关的问题时非常有用。 #### 引用[.reference_title] - *1* *3* [Python递归的几个经典案例](https://blog.csdn.net/qdPython/article/details/126748787)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [递归算法及经典例题详解](https://blog.csdn.net/weixin_45881074/article/details/120585865)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值