【动态规划】【数字金字塔】【最大子段和】【0-1背包问题】【0-1背包问题的滚动数组优化】【0-1背包问题的一位数组优化】【青蛙跳台阶——动态规划解法】

目录

【动态规划的条件】

【数字金字塔】

【最大子段和】

【0-1背包问题】

【0-1背包问题动态规划的四要素】

 【算法优化1——滚动数组优化】

 【算法优化2——优化到一维数组】

 【青蛙跳台阶——动态规划解法】


【动态规划的条件】

 (1)最优子结构原问题的最优解是通过子问题的最优解得到的;

(2)无后效性前面状态的决策不会限制后面状态的决策;

(3)重复子问题:一个子问题可以被重复利用到多个父亲状态中。

【关键】舍空间而取时间


【数字金字塔】

【题目描述】

        给定一个n层的金字塔,求一条从最高点到底层任意点的路径使得路径经过的数字之和最大。每一步可以走到左下方的点也可以到达右下方的点)

【思路】

 【将思路转换为代码】

for(int i=1;i<=n;i++)
{
    for(int j=1;j<=i;j++)
    {
        if(j==1)
            f[i][j]=f[i-1][j]+a[i][j];//表示在对角线上的元素
        else
            if(i==j)
                f[i][j]=f[i-1][j-1]+a[i][j];//表示在最左侧一列的元素
            else
                f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];//表示在除了上述两种情况以外
                                                           //的其他位置元素
    }
}

【代码优化——条件合并】

for(int i=1;i<=n;i++)
    for(int j=1;j<=i;j++)
        f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];
            //之所以可以合并三种情况,是因为金字塔中的数字都为正整数,
            //那么在i==j或j==1这两种情况下,max函数不会选择那个不可以调用的位置

 【复杂度分析】

时间复杂度:O(n^{2})(双层for循环)

空间复杂度:O(n^{2})(开设了二维数组)


【最大子段和】

        给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。

b[0]=a[0];
for(int i=1;i<n;i++)
{
    if(b[i-1]>0)//如果上一段子段和>0,说明对整体的子段和相加有益处,需要保留上一段子段和
        b[i]=b[i-1]+a[i];
    else//否则就将子段和重新计算,将b[i]置为a[i]
        b[i]=a[i];
}

【0-1背包问题】

【题目描述】给定n个物品,每个物体有个体Vi和一个价值Pi。现有一个容量为V的背包,请问如何选择物品装入背包,使得获得的总价值最大?

 【思路】

        通过讨论每个物品放与不放,连接前 i -1 个物品的状态和前 i 个物品状态之间的关系,最终结果就是两种选择下,收益的更大值

我们维护一个二维状态 f [ i , j ], 来表示前 i 个物品,放到体积为 j 的背包里

可以得到:f [ i , j ] = max( f [ i − 1, j ] , f [ i −1, j − v [ i ] ] + p [ i ]


【0-1背包问题动态规划的四要素】

(1)状态:一个二维状态 f [ i , j ], 来表示前 i 个物品,放到体积为 j 的背包里

(2)转移方程:

f[i][j]=f[i-1][j];//表示装不下第i个物品
f[i][j]=max(f[i-1][j],f[i-1,j-v[i]]+p[i]);

(3)初始状态:

f[0][j]=0;//表示一个物品都没放,价值为0

(4)转移方向:保证 i 从小到大增大,等式右边的状态比等式左边先算出来。

完整代码如下:

#include<iostream>
#include<algorithm>
#define N 1002
using namespace std;
int n, V, v[N], p[N],f[N][N];
int main()
{
	cin >> n >> V;
	for (int i = 1; i <= n; i++)
		cin >> v[i] >> p[i];
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= V; j++)
		{
			if (j < v[i])
				f[i][j] = f[i - 1][j];
			else
				f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + p[i]);
		}
	}
	cout << f[n][V]<<endl;
	return 0;
}

【复杂度分析】

空间复杂度:O(nV)使用了二维数组 f [ n ][ V ]

时间复杂度:O(nV)双层for循环

【缺点分析】

        因为这个算法与物品个数,背包容量有关,假如物品个数很多,物品体积也非常大的时候,空间复杂度会急剧增加


 【算法优化1——滚动数组优化】

【基本思想】

        在动态规划中,有时候内存空间会比较紧张,所以我们需要一些技巧来优化内存开销,下面提出一种优化方式为“滚动数组优化”,其基本思想类似于“踩石头过河”

        而在此题中,当我们在计算第 i 行时,只需保留第 i -1 行,可以把前 i - 2 行的内存空间释放掉,那么也就是说每一次计算只需要两行的数据。那么我们可以只利用 f [ 2 ][ V ]来记录数组的状态。奇数行填入状态f [ 1 ][ j ]中,偶数行填入状态f [ 0 ][ j ]中。

 代码修改如下:

int f[2][V];
for(int i=1;i<=n;i++)
    for(int j=1;j<=V;j++)
        if(j<v[i])
            f[i&1][j]=f[(i-1)&1][j];
        else
            f[i&1][j]=max(f[(i-1)&1][j],f[(i-1)&1][j-v[i]]+p[i]);
cout<<f[n&1][V]<<endl;

【注意】

(1)奇数的二进制表示的最低位为“1”,偶数的最低位为“0”,可以利用 i & 1 来取 i 的奇偶性

i & 1 = 1 ( i 为奇数)

i & 1 = 0( i 为偶数)

(2)利用 i & 1 来取 i 的奇偶性,为什么不用 i % 2 呢?因为位运算的优先级最低,但是运算速度却最高,用 i & 1来判断奇偶性比用 i % 2 要高4倍,当循环的次数非常大时,位运算是非常有效率的

【复杂度分析】

空间复杂度降为O(2V)


 【算法优化2——优化到一维数组】

【基本思路】

//j<V时
if (j < v[i])
	f[i][j] = f[i - 1][j];
else
	f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + p[i]);

        根据上面代码可以看出来,当 j < v [ i ]时,f [ i ][ j ] = f [ i - 1 ][ j ],那么如果我们将 j 从大到小进行枚举当 j 从 V 变化到 v[ i ]的过程中,一直记录的是:

                f [ i ][ j ] = max(f [ i - 1 ][ j ], f [ i - 1 ][ j - v [ i ] ] + p [ i ] )

        当 j < v [ i ] 时一直都有f [ i ][ j ] = f [ i - 1 ][ j ],那么如果映射到一维数组的话,相当于没有变化。

        所以我们维护一个一维数组f [ j ]当 j < v [ i ]时,f [ j ]记录的就是f [ i - 1, j ],当 j > v [ i ]时,f [ j ]记录的就是f [ i ,j ]

采用代码如下:

int f[N];//维护一个一维数组
for(int i=1;i<=n;i++)
    for(int j=V;j>=v[i];j--)//j从V开始枚举到v[i],1~v[i]的状态都是一样的,f[i,j]=f[i-1,j]
                            //可以用v[i]的状态来代表v[i]之前的状态
        f[j]=max(f[j],f[j-v[i]]+p[i]);
cout<<f[V]<<endl;

【复杂度分析】

空间复杂度降为O(V)


 【青蛙跳台阶——动态规划解法】

【题目描述】

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

        这道题目我曾经在一篇文章中用递归和递推的方法阐述过,在这里将用动态规划的思想阐述一下。之前文章放在下面啦,有需要可以参考呀!

(1条消息) 【经典面试题——青蛙跳台阶】【递归/for循环做法】【跳上n级台阶】【跳上m级台阶】_捡到一只姜小鱼的博客-CSDN博客https://blog.csdn.net/qq_46480277/article/details/124402004?spm=1001.2014.3001.5501【动态规划思路】

(1)青蛙若是想跳到第 n 个台阶,那么最后一步只有两种跳法,跳一步或者跳两步,那么分别有

f ( n -1 )和 f ( n -2 )种跳法,所以一共有 f ( n)= f ( n-1)+ f ( n-2)种跳法;

(2)初始条件为:f (0)=1

                               f (1)=1;

                               f (2)=2;

(3)定义一维数组dp,dp [ i ]代表青蛙跳到第 i 个台阶时,需要的步数,dp[ i ]=dp[ i-1]+dp[ i-2];

采用代码段如下:

int dp[N];
for(int i=2;i<=n;i++)
    dp[i]=dp[i-1]+dp[i-2];
cout<<dp[n]<<endl;

动态规划问题就先聊到这里啦,以后学到更多的话还会继续po到博客上的!

撒花完结! 

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数字金字塔问题是一个经典的动态规划问题,它可以用来求解给定数字金字塔的最大路径和。下面是一个使用C语言实现的示例代码: ```c #include <stdio.h> #define MAX_SIZE 100 int max(int a, int b) { return (a > b) ? a : b; } int maxPathSum(int pyramid[MAX_SIZE][MAX_SIZE], int n) { // 从倒数第二行开始向上计算最大路径和 for (int i = n - 2; i >= 0; i--) { for (int j = 0; j <= i; j++) { // 当前位置的最大路径和等于下一行相邻两个位置的较大值加上当前位置的值 pyramid[i][j] += max(pyramid[i + 1][j], pyramid[i + 1][j + 1]); } } // 最终结果保存在金字塔的顶部 return pyramid[0][0]; } int main() { int n; printf("请输入数字金字塔的行数:"); scanf("%d", &n); printf("请输入数字金字塔的元素:\n"); int pyramid[MAX_SIZE][MAX_SIZE]; for (int i = 0; i < n; i++) { for (int j = 0; j <= i; j++) { scanf("%d", &pyramid[i][j]); } } int maxSum = maxPathSum(pyramid, n); printf("最大路径和为:%d\n", maxSum); return 0; } ``` 这段代码中,我们首先定义了一个`max`函数,用于返回两个数中的较大值。然后,我们定义了一个`maxPathSum`函数,用于计算数字金字塔的最大路径和。在`maxPathSum`函数中,我们使用两层循环来遍历金字塔的每个位置,并根据动态规划的思想计算出最大路径和。最后,在`main`函数中,我们通过用户输入构建数字金字塔,并调用`maxPathSum`函数来求解最大路径和。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值