动态规划——初步学习笔记

初学算法,通过csdn等相关的文章,资料的学习做一些笔记。

定义:

动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。
动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

基本思想与策略编辑:

由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
(来自百度百科)

译成人话:

首先是拆分问题,我的理解就是根据问题的可能性把问题划分成一步一步这样就可以通过递推或者递归来实现.
关键就是这个步骤,动态规划有一类问题就是从后往前推到,有时候我们很容易知道:如果只有一种情况时,最佳的选择应该怎么做.然后根据这个最佳选择往前一步推导,得到前一步的最佳选择
然后就是定义问题状态和状态之间的关系,我的理解是前面拆分的步骤之间的关系,用一种量化的形式表现出来,类似于高中学的推导公式,因为这种式子很容易用程序写出来,也可以说对程序比较亲和(也就是最后所说的状态转移方程式)
我们再来看定义的下面的两段,我的理解是比如我们找到最优解,我们应该讲最优解保存下来,为了往前推导时能够使用前一步的最优解,在这个过程中难免有一些相比于最优解差的解,此时我们应该放弃,只保存最优解,这样我们每一次都把最优解保存了下来,大大降低了时间复杂度

————————————————
出处:CSDN博主「BS有前途」的原创文章
原文链接:https://blog.csdn.net/ailaojie/article/details/83014821

案例一:

问题描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int n;   //台阶的数量
	scanf("%d",&n);
	int d[n];
	d[0]=1;
	d[1]=2;
	for(int i=2;i<n;i++)
	{
		d[i]=d[i-1]+d[i-2]; 
	}
	printf("%d",d[n-1]);    //青蛙跳n阶台阶的种数 
	return 0;
}

我认为这个例题就是基于著名的 Fibonacci 数列
案例二:

问题描述: 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 问总共有多少条不同的路径?

#include<stdio.h>
#include<stdlib.h>
int main() {
	int m,n;  //网格的长与宽
	scanf("%d%d",&m,&n);
	int d[m][n];
	for(int i=0; i<m; i++) {
		d[i][0]=1;
	}
	for(int j=0; j<n; j++) {
		d[0][j]=1;
	}
	for(int i=1;i<m;i++)
	{
		for(int j=1;j<n;j++)
		{
			d[i][j]=d[i-1][j]+d[i][j-1];
		}
	}
	printf("%d",d[m-1][n-1]);
}

注:一定要根据动态规划的定义来分解题目,不然很难理解这段代码:

for(int i=0; i<m; i++) {
		d[i][0]=1;
	}
	for(int j=0; j<n; j++) {
		d[0][j]=1;
	}

实际上就是先将初始点的数据初始化,在叠加到之后的位置上。
案例三:

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

举例: 输入: arr = [ [1,3,1], [1,5,1], [4,2,1] ] 输出: 7 解释: 因为路径
1→3→1→1→1 的总和最小。

#include<stdio.h>
#include<stdlib.h>

int min(int x,int y);

int main() {
	int m,n;  //网格的长与宽
	scanf("%d%d",&m,&n);
	int d[m][n];
	int arr[m][n];
	for(int i=0; i<m; i++) {
		for(int j=0; j<n; j++) {
			scanf("%d",&arr[i][j]);
		}
	}
	d[0][0]=arr[0][0];
	for(int i=1; i<m; i++) {
		d[i][0]=d[i-1][0]+arr[i][0];
	}
	for(int j=1; j<n; j++) {
		d[0][j]=d[0][j-1]+arr[0][j];
	}
	for(int i=1; i<m; i++) {
		for(int j=1; j<n; j++) {
			d[i][j]=min(d[i-1][j],d[i][j-1])+arr[i][j];
		}
	}
	printf("%d",d[m-1][n-1]);
}
int min(int x,int y) {
	if(x<y) {
		return  x;
	} else {
		return y;
	}
}

与上题不同的就是添加了每个子问题的最优解(该问题就是每个子位置的最短路径)。
案例四:

问题描述

给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符

示例 1: 输入: word1 = “horse”, word2 = “ros” 输出: 3 解释: horse -> rorse (将
‘h’ 替换为 ‘r’) rorse -> rose (删除 ‘r’) rose -> ros (删除 ‘e’)


01背包问题

根据动态规划解题步骤(问题抽象化、建立模型、寻找约束条件、判断是否满足最优性原理、找大问题与小问题的递推关系式、填表、寻找解组成)找出01背包问题的最优解以及解组成,然后编写代码实现。

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<math.h>

int max(int a,int b);

int main()
{
	int w[5]={0,2,3,4,5};
	int v[5]={0,3,4,5,6};
	int bagV=8;
	int dp[5][9]={{0}};
	
	for( int i=1;i<=4;i++)
	{
		for(int j=1;j<=bagV;j++)
		{
			if(j<w[i])
			{
				dp[i][j]=dp[i-1][j];
			}else
			{
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
			}
		}
	}
	for(int i=0;i<5;i++)
	{
		for(int j=0;j<9;j++)
		{
			printf("%d ",dp[i][j]);
		}
		printf("\n");
	}
	printf("%d",dp[4][8]);
}
int max(int a,int b)
{
	return a>b?a:b;
}

回溯求背包最优解

2020蓝桥杯第5题矩阵:

把 1 ∼ 2020 放在 2 × 1010
的矩阵里。要求同一行中右边的比左边大,同一列中下边的比上边的大。一共有多少种方案?答案很大,你只需要给出方案数除以 2020 的余数即可。

网上查询的有三种答案,我都没有看懂。
第一种:

#include<stdio.h>
#include<stdlib.h>

int dp[1020][1020]; 

int main() {
	dp[0][0]=1;
	int i,j;
	for(i=0;i<=1010;i++)
	{
		for( j=0;j<=1010;j++)
		{
			if(i-1>=j)
			{
			dp[i][j]+=dp[i-1][j]%2020;
			}
			if(j)
			{
				dp[i][j]+=dp[i][j-1]%2020;
			}
		}
	}
	printf("%d",dp[1010][1010]);
}

第二种:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1020;
int dp[maxn][maxn];//填到dp[i][j]第1行有i个数,第二行有j个数的方案个数 
int main()
{
	dp[0][0]=1;
	for(int i=0;i<=1010;i++)
	{
		for(int j=0;j<=i;j++)
		{
			if(i>=1 && j>=1) dp[i][j]=dp[i-1][j]+dp[i][j-1];
			else if(i>=1 && j<1) dp[i][j]=dp[i-1][j];
			else if(i<1 && j>=1) dp[i][j]=dp[i][j-1];
			else dp[i][j]=1;
			dp[i][j]=dp[i][j]%2020;
		}
	}
	cout<<dp[1010][1010];

}

第三种是卡特兰数的解法

#include<stdio.h>
#include<memory.h>
int dp[1020];
int main()
{
	memset(dp, 0, sizeof(dp));
	dp[0]=1, dp[1]=1; 
	for(int i=2; i<=1010; i++)
	for(int j=0; j<=i; j++)
		dp[i] = (dp[i]+dp[i-j-1]*dp[j])%2020;
	printf("%d", dp[1010]);
	
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值