《算法:递归+记忆化搜索》

发布于:2025-04-01 ⋅ 阅读:(60) ⋅ 点赞:(0)

递归+记忆化搜索

此文章为简单讲义,详情请移步至主播的主页算法合集:

樱茶喵的个人主页

🔴递归

一.什么是递归?

函数自己调用自己。

二.为什么要用递归?

优点:

  • 代码简洁,可读性好

  • 可用于某些排序算法(归并)和二叉树的遍历,大大简化代码。

不足:

调用函数的开销很大,每次调用都会在栈上为其分配空间,容易栈溢出(Stack Overflow),也就是我们俗称的爆栈。

例:(伪代码)

(1).斐波那契数

void Fib(int n)
{
	if(n == 0 || n == 1)
	return n;
	else
	return Fib(n-2) + Fib(n-1);
}

(2).归并排序

void Merge(int* nums,int left,int right)
{
    if(left >= right)
        return;
    int mid = (left + right)/2;
    Merge(nums,left,mid);
    Merge(nums,mid+1,right);
    合并左右的两个有序数组。
}

(3).二叉树的遍历…

三.怎么理解递归?

本质:

主问题->子问题

子问题->子子问题…

最终将主问题转换为最小子问题,再往上返回。

主问题和子问题、子问题和子问题之间的特点:

都有相同的映射关系f(可以用f来解决所有的子问题)

  • 递归展开的细节图(时间复杂度:O(2^n)
  • 举例说明(斐波那契数)

宏观看待递归的过程:

  1. ​不要太纠结于递归的细节展开图。
  2. 把递归过程想成一个黑盒。
  3. 坚信这个黑盒一定能完成我们赋予他的任务。

四.如何写递归?

1.先找到相同的子问题(函数头)(映射关系)

2.在实现递归过程中只用关心其中一个子问题是如何解决的,因为所有子问题的映射关系是一样的。(函数体的设计)

3.注意递归结束的条件。


🔴记忆化搜索

一.什么是记忆化搜索

记忆化的解释:

就是带备忘录的递归。(容器、数组、哈希表…)

将出现过的子问题的答案存到一个“备忘录”里,之后在调用函数时如果发现该问题已经出现过,则可以在备忘录里找到该问题的答案,直接返回。

二.如何实现记忆化搜索?


1.添加一个备忘录
2.递归每次返回的时候,将结果放到备忘录里面
3.在每次进入递归的时候,往备忘录里面瞅一瞅

斐波那契数举例
class Solution {
public:
    int memo[31];//创建备忘录
    int fib(int n) 
    {
        memset(memo,-1,sizeof memo);//初始化为-1,因为递归过程中的答案不可能为-1
        return dfs(n);
    }
    
    int dfs(int n)
    {
        //"剪枝",判断该子问题是否已出现过,出现则直接返回答案,提升效率最关键的一步
        if(memo[n] != -1)
        return memo[n];

        if(n == 0 || n == 1)
        {
            memo[n] = n;
            return n;
        }

        memo[n] = dfs(n-1)+dfs(n-2);//将子问题答案存到备忘录中
        return memo[n];
    }
};

通过画递归的细节图可以发现,细节图的构成类似于二叉树,查找备忘录的过程就是剪枝的过程。在这一番操作之后,使时间复杂度:从O(2^n)转化为O(n),大大提高了运行效率。


网站公告

今日签到

点亮在社区的每一天
去签到