动态规划(C语言)

本文介绍了动态规划的概念,以斐波那契数列为起点,解释了递归算法的时间复杂度问题,并展示了如何通过动态规划避免重复计算。接着,通过01背包问题、最长上升子序列和数字三角形三个实例深入讲解动态规划的解决步骤和思想。最后,提到了一个类似数字三角形的跳跃问题,进一步巩固动态规划的应用。
摘要由CSDN通过智能技术生成

一、入门

 以斐波那契数列为例,它的第一项为1,第二项为1,从第三项开始,每一项的值都是前面两项的和。让我们求第n项的是多少。对于这个问题,我们从最开始的递归思想来看。

int fib(int n)
{
	if (n == 1 || n == 2) return 1;
	return fib(n - 1) + fib(n - 2);
}

这个递归的时间复杂度是O(2^n),随着n的增大,呈指数级别增长。递归的时间复杂度之所以高,是因为它涉及了很多重复性计算。比如在计算fib(5)的时候,fib(5) = fib(4) + fib(3)。接下来计算fib(4),fib(4) = fib(3) + fib(2)。fib(4)计算完后就会再计算fib(3)。然而我们之前在计算fib(4)的时候已经算出fib(3)了。fib(3)被重复计算了两次。当n很大的时候。重复计算的工作就越来越多。

为了避免重复计算,我们可以在计算的过程中将已经计算过的值保存下来,等到再一次遇到的时候就可以直接使用。开一个dp[]数组,dp[i]存储fib(i)的值。dp[i] = 0表示还没有计算过

int dp[20];
int fib(int n)
{
	if (n == 1 || n == 2) return 1;
	if (dp[n]) return dp[n];//计算过,直接返回dp[n]
	return dp[n] = fib(n - 1) + fib (n - 2);
}

使用数组储存状态是动态规划必备的要素。

动态规划问题解决的基本思想:

1、根绝问题所求的那一项和变量的个数,确定是一维数组,二维数组或者多维数组。

2、写出初始值,一般是某个变量为1或者0 的特殊情况时候的解。

3、通过循环,一般是两个循环中间每一层循环表示一个变量的递增情况。

4、在循环中间写出对应的递推关系表达式,求出问题的每一项。

5、根据题意得到答案,一般是数组的最后一项。

二、DP思想入门

1、01背包问题https://www.lanqiao.cn/problems/1174/learning/

#include <stdio.h>
#define max(x,y) (x>y?x:y)
int main(void)
{
  int n,V;
  scanf("%d %d",&n,&V);
  int w[105]={0},v[105]={0};//分别储存重量和价值的数组 
  int dp[105][1005]={0};
  for(int i=1;i<=n;i++)
    scanf("%d %d",&w[i],&v[i]);
  for(int i=1;i<=n;i++)//商品数量上限是n 
    for(int j=1;j<=V;j++)//物品体积和不能超过背包容量V 
    {
      if(j<w[i])
        dp[i][j]=dp[i-1][j];
        dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
    }
  printf("%d",dp[n][V]);
  return 0;
}

该情况下的递推关系,x不选,v选该物品

顶部f(4,8)是一开始的状态,按层数往下依次是编号4、3、2、1的物品选不选的问题

下面这张图:(别把横纵坐标搞反了,第一行是纵坐标,第一列是横坐标)

纵坐标是背包容量;横坐标是物品可以拿的数量      值是最大价值

5 10
w v数组
1 6
2 5
3 8
5 15
3 3
dp数组
0 0 0 0 0 0 0 0 0 0 0
0 6 6 6 6 6 6 6 6 6 6
0 6 6 11 11 11 11 11 11 11 11
0 6 6 11 14 14 19 19 19 19 19
0 6 6 11 14 15 21 21 26 29 29
0 6 6 11 14 15 21 21 26 29 29
29

2、最长上升子序列

https://www.dotcpp.com/oj/problem1557.html

#include <stdio.h>
#define max(x,y) (x>y?x:y)
int main()
{
	int n,m=0;
	int a[1000]={0};//储存原数组
	int dp[1000]={0};//储存从不同位置开始最长长度 
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
		
	for(int i=1;i<=n;i++)
	{
		dp[i]=1;
		for(int j=1;j<i;j++)
		{
			if(a[j]<=a[i])
			    dp[i]=max(dp[j]+1,dp[i]);
		}
		m=max(m,dp[i]);
		printf("%d ",m);
	}
	printf("\n%d",m);
	return 0; 
}

思路分析:因为最短也是1,所以说dp数组赋值为1,

从第一个数开始,依次找到以该数为尾的数组的最长上升子序列长度储存在dp数组中

内循环中,依次从第一个数开始遍历,找到比a[i]小的a[j],并且dp[j]最大的数,然后+1赋值给dp[i].

其方程为dp[i]=max(dp[j])+1,下面为输出结果,第三行为dp数组

10
3 18 7 14 10 12 23 41 16 16
1 2 2 3 3 4 5 6 6 6
6

3、数字三角形

https://www.lanqiao.cn/problems/505/learning/

#include<stdio.h>
#include <stdlib.h>
#define max(x,y) (x>y?x:y)

int main() 
{
	int n;
    scanf("%d", &n);
    int An[n][n];//储存三角形每个位置的值 
    
    for(int i = 0; i < n; i ++)//三角形的行i的循环 
	{
        for(int j = 0; j < i + 1; j ++)//三角形列j的循环,列最大等于该行的行数 
            scanf("%d", &An[i][j]);
    }
    for(int i = 0; i < n; i ++)//三角形的行i的循环 
	{
        for(int j = 0; j < i + 1; j ++)//三角形列j的循环,列最大等于该行的行数 
		{
            if(i >= 1)//不是第一列只有一个数的情况下 
			{
                if(j == 0)//数组第一列没有左上角的值,直接加上面的值就是最大值 
                    An[i][j] += An[i -1][j];
                else if(j == i)//即数组每列最后一个没有正上方的值,直接加左上值即可 
                    An[i][j] += An[i - 1][j - 1];
                else//其余情况加其左上右上的最大值 
				{
                    int max1 =Max(An[i - 1][j - 1],An[i - 1][j]); 
                    An[i][j] += max1;
                }
            }
        }
    }
    //上面执行完后,An数组每个值表示顶点到该位置路程最大值
    //向左下走与向右下走的次数相差不能超过 1,即输出第n行中间值,分行数的奇偶
	if(n%2==1)  
		printf("%d\n",An[n-1][(n-1)/2]);
    else 
		printf("%d\n",Max(An[n-1][(n-1)/2],An[n-1][(n-1)/2+1]));
    return 0;
}

思路分析:从顶点开始,遍历输入的二维数组,将顶点到该点路径最大值重新给数组该点赋值,题目中说向左下走的次数与向右下走的次数相差不能超过 1。即规定终点只能是最后一排的中间(最后一行为奇数则最中间,偶数则中间两个都可)。

顶点的值就是该点的值,所以说未讨论,如果是像上面几个题一样重新开创新数组顶点要单独赋值,注意本题是将三角形全部靠左对齐,所以说左边一列无左上角值,右边一列无右上角值的下标和原三角形比起来略有差异。

4、跳跃

https://www.lanqiao.cn/problems/553/learning/

#include <stdio.h>
int b[105][105];//每个点的权值
int find_max(int x,int y){
  int max=0;
  for(int i=x;i>=1;i--){//从下往上倒推
    for(int j=y;j>=1;j--){//从右往左倒推
      if(!(x==i&&y==j)&&(x-i+y-j)<=3&&max<b[i][j]){
        //同时满足三个条件
        //1.不原地踏步2.每次跳跃在距离范围之内3.比当前max更大时才更新max
        max=b[i][j];
      }
    }
  }
  return max;
}
int main(){
  int n,m;
  scanf("%d %d",&n,&m);
  int i,j;
  for(i=1;i<=n;i++){
    for(j=1;j<=m;j++)
      scanf("%d",&b[i][j]);
  }
  for(i=1;i<=n;i++){
    for(j=1;j<=m;j++)
      b[i][j]+=find_max(i,j);
  }
  printf("%d",b[n][m]+b[1][1]);
}

思路分析:第三题的衍生题,三角形变为矩阵,每次移动的选择更多,但思路相同,都需要找出顶点到每个点路径的最大值,并且储存在数组中。最后输出数组终点的值即可。

数字三角形是本身加上旁边两个相邻位置的最大值,此处也一样,一个点能到的是其右下角半径<=3的圆内的点,所以说一个点的值应该是该点值加上其左上角扇形内的最大值。所以说在find-max函数内是倒序遍历

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值