【一周编程学习】--5.递归与动态规划学习笔记

1.递归

1.1 递归,简单点来说,就是一个函数直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。

        我们可以把” 递归 “比喻成 “查字典 “,当你查一个词,发现这个词的解释中某个词仍然不懂,于是你开始查这第二个词。可惜,第二个词里仍然有不懂的词,于是查第三个词,这样查下去,直到有一个词的解释是你完全能看懂的,那么递归走到了尽头,然后你开始后退,逐个明白之前查过的每一个词,最终,你明白了最开始那个词的意思。1.

//经典递归案例:n的阶乘
int Factorial(int n)
{
    if(n==0)
        return 1;
    else
        return n*Factorial(n-1);
}

 

1.2 递归的过程是出入栈的过程,利用入栈和出栈模拟递归的过程。

如上例所示:

(1)当n=3时,Factorial(3)调用 Factorial(2),Factorial(2)调用 Factorial(1),Factorial(1)调用 Factorial(0)。

(2) Factorial(0)是递归终止条件,此时栈的高度是3,停止入栈。

(3)计算 Factorial(0),开始出栈,依次计算 Factorial(1)到 Factorial(3),递归结束。

!注意:正常情况下,我们的思维方式是,已知 Factorial(0),乘1得 Factorial(1)。。。最后得到 Factorial(n);而递归的思考方式是相反的,通过 Factorial(n)逐步倒推得到终止条件 Factorial(0),再由 Factorial(0)逐步计算得到 Factorial(n)。

1.3 验证递归是否正确的条件

  1. 当 n=0,1 时,结果正确;
  2. 假设递归对于 n 是正确的,同时对于 n+1 也正确。
// 汉诺塔问题
void Hanoi(int n,char a,char b,char c)
{
    if(n==1)
    {
        cout<<a<<"-->"<<c<<endl;
    }
    Hanoi(n-1,a,c,b);
    Hanoi(1,a,b,c);
    Hanoi(n-1,b,a,c);
}
//二叉树节点个数的展开
typedef struct BinNode
{
    char data;
    BinNode* left;
    BinNode* right;
}BinNode,*BinTree;
int GetNodes(BinNode *node)
{
    if(node==NULL)
        return 0;
    return
        GetNodes(node->left)+GetNodes(node->right)+1;
}

1.4 可使用递归的情况

  1. 子问题需与原问题为同样的事,且规模更小;
  2. 程序停止条件。

2.动态规划

动态规划的核心思想是备忘录法,将递归中的调用结果保存下来,在需要时直接查找,而不是重新计算,这样就节省了大量的时间。

2.1 DP的解题步骤:

  • 将原问题分解为子问题。

子问题与原问题形式相似,规模变小。子问题的解一旦被求出会被保存,只求解一次。

  • 确定状态。

与子问题相关的各个变量的一组取值,称为一个“状态”。在这个状态下的值,对应着子问题的解。

所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。

  •  确定一些初始状态(边界状态)的值

 以“数字三角形”为例,初始状态就是底边数字,值就是底边数字值

  •     确定状态转移方程

如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。

 

2.2 例题: 

例题:从数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。 三角形的行数大于1小于等于100,数字为 0 - 99

 首先,肯定得用二维数组来存放数字三角形, 然后我们用D( r, j) 来表示第r行第 j 个数字(r,j从1开始算),用MaxSum(r, j)表示从D(r,j)到底边的各条路径中,最佳路径的数字之和。因此,此题的最终问题就变成了求 MaxSum(1,1)。

//递归版本
#include<iostream>
#include<algorithm>
using namespace std;

#define MAX 101

int D[MAX][MAX];

int MaxSum(int r,int j)
{
	if(r==n)
		return D[r][j];
	int x=int D[r+1][j];
	int y=int D[r+1][j+1];
	return max(x,y)+D[r][j];
}

int main()
{
	int i,j,n;
	cin>>n;
	for(i=1;i<=n;i++){
		for(j=1j<=i;j++)
			cin>>D[i][j];
	}
	cout<<MaxSum(1,1)<<endl;
}
//递归--》递推
#include<iostream>
#include<algorithm>
using namespace std;

#define MAX 101
int n;
int D[MAX][MAX];
int *maxSum;

int main()
{
	int i,j;
	cin>>n;
	for(i=1;i<=n;i++){
		for(j=1;j<=i;j++)
			cin>>D[i][j];
	}
	//maxSum指向第n行
	maxSum=D[n];
	for(int i=n-1;i>=1;i--){
		for(int j=1;j<=i;j++)
			maxSum[j]=max(maxSum[j],maxSum[j+1])+D[i][j];
	}
	cout<<maxSum[1]<<endl;
	return 0;
}

2.3 递归到DP的一般转化方法

 递归函数有n个参数,就定义一个n维的数组,数组的下标是递归函数参数的取值范围,数组元素的值是递归函数的返回值,这样就可以从边界值开始, 逐步填充数组,相当于计算递归函数值的逆过程。


 

参考:

1:https://blog.csdn.net/baidu_28312631/article/details/47418773

2.https://blog.csdn.net/u013309870/article/details/75193592

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值