【深圳大学算法设计与分析】实验四 动态规划—鸡蛋掉落问题

实验目的

(1)掌握动态规划算法设计思想。

(2)掌握鸡蛋坠落问题的动态规划解法。

实验内容与结果

动态规划

将问题划分为更小的子问题,通过子问题的最优解来重构原问题的最优解。动态规划中的子问题的最优解存储在一些数据结构中,这样我们就不必在再次需要时重新处理它们。任何重复调用相同输入的递归解决方案,我们都可以使用动态规划对其进行优化。

鸡蛋掉落问题

用鸡蛋确认在多高的楼层鸡蛋落下来会破碎,这个恰好使鸡蛋破碎的楼层叫门槛层。门槛楼层以下的任何楼层掉落的鸡蛋不会破碎。给定建筑物的一定数量的楼层(比如 f 层)和一定数量的鸡蛋(比如 e 鸡蛋),找出门槛层必须执行的最少的鸡蛋掉落试验的次数。

问题约束条件

  1. 从跌落中幸存下来的鸡蛋可以再次使用。
  2. 破蛋必须丢弃。
  3. 摔碎对所有鸡蛋的影响都是一样的。
  4. 如果一个鸡蛋掉在地上摔碎了,那么它从高处掉下来也会摔碎。
  5. 如果一个鸡蛋在跌落中幸存下来,那么它在较短的跌落中也能完整保留下来。

实验要求

1、给出解决问题的动态规划方程;

2、随机产生f,e的值,对小数据模型利用蛮力法测试算法的正确性;

3、随机产生f ,e的值,对不同数据规模测试算法效率,并与理论效率进行比对,请提供能处理的数据最大规模,注意要在有限时间内处理完;

4、该算法是否有效率提高的空间?包括空间效率和时间效率。

实验步骤

1、给出解决问题的动态规划方程;

状态转移方程:dp[i][j] = 1 + min(max(dp[x-1][j-1], dp[i-x][j])),其中x的取值范围为[1, i]

状态转移方程的含义是,在确定dp[i][j]时,需要选择一个最优的x值,使得在两种情况下所需的尝试次数最小化。

鸡蛋破碎:在第 x 层扔鸡蛋,鸡蛋破碎,需要在[1, x-1]的楼层范围内继续使用j-1个鸡蛋进行尝试,即dp[x-1][j-1]。

鸡蛋未破碎:在第 x 层扔鸡蛋,鸡蛋未破碎,需要在[x+1, i]的楼层范围内继续使用j个鸡蛋进行尝试,即dp[i-x][j]。

在这两种情况中,选择使得尝试次数最大的情况,并在所有可能的x值中取最小值。

2、随机产生fe的值,对小数据模型利用蛮力法测试算法的正确性;

蛮力法思想:从第K层向上进行尝试,如果鸡蛋碎了,则向下尝试;若没碎,则向上尝试。

核心伪代码如下

可以看出,蛮力法需要尝试的次数是非常多的,当数据规模较大时,非常耗时。

3、随机产生f e的值,对不同数据规模测试算法效率,并与理论效率进行比对,请提供能处理的数据最大规模,注意要在有限时间内处理完;

核心伪代码如下

对于不同的数据规模,取十组数据耗时的平均值为最终耗时。

在动态规划算法中,由于对前面运算的结果会进行存储,所以会极大的提高后面的数据的运算效率。如下图所示,第3组和第4组甚至不需要计算就直接得到了结果。

运算结果如下

当数据规模达到5000时,很长时间都未有结果。

算法复杂度分析:

1. 填充dp数组的循环嵌套:

   - 外层循环从1到N,共N次迭代。

   - 内层循环从2到K,共K-1次迭代。

   - 内部还有一个循环从1到i,共i次迭代。

   - 总迭代次数为 ∑(i=1 to N) ∑(j=2 to K) i = N * (K-1) * (N+1) / 2。

2. 总体复杂度:

   - 时间复杂度:O(N * K * (N+1)/2),近似为 O(N^2 * K)。

   - 空间复杂度:O(N * K),需要使用二维数组dp[N+1][K+1]存储结果。

当N和K较小时,算法的时间复杂度还在可接受的范围内。然而,如果N和K的值非常大,可能会导致较长的运行时间和较大的空间开销。因此,在实际应用中,需要对算法进行进一步优化,以降低时间和空间复杂度。

表格如下

绘图如下

由图可知,理论时间和实际时间基本吻合,说明时间复杂度分析正确。之所以实际时间会大于理论时间,一个原因可能是作为基准的首个点存在误差,另一个原因时间复杂度在近似时减少了些许。

4、该算法是否有效率提高的空间?包括空间效率和时间效率。

为了优化鸡蛋掉落问题的动态规划算法以降低时间和空间复杂度,可以采用以下两种常见的优化方法:

1. 二分查找优化:

   在填充dp数组时,可以使用二分查找来寻找最佳的尝试楼层。

   对于固定的i和j,假设我们选择在第x层进行尝试(1 <= x <= i)。

   如果鸡蛋在第x层破碎了,那么剩下的问题转化为在前x-1层楼使用j-1个鸡蛋的子问题,即dp[x-1][j-1]。

   如果鸡蛋在第x层没有破碎,那么剩下的问题转化为在第x层及以上楼层使用j个鸡蛋的子问题,即dp[i][j] = dp[i-x][j]。

   因此,在当前层x的两种情况下,尝试次数的最大值为1 + max(dp[x-1][j-1], dp[i-x][j])。

   通过二分查找在区间[1, i]内找到使得尝试次数最小的x值,可以减少内层循环的次数。

2. 空间优化:

   在原始的动态规划解法中,使用了一个二维数组dp[N+1][K+1]来存储结果,占用了O(N * K)的空间。

   可以进行空间优化,仅使用两个一维数组dp1[K+1]和dp2[K+1]来交替存储状态,减少空间占用。

   在状态转移过程中,当前状态仅依赖于上一层的状态,因此只需要两个数组来保存当前层和上一层的状态即可。

通过应用这些优化方法,可以显著降低算法的时间和空间复杂度,提高算法的效率。

实验总结

蛮力法是非常耗时,效率非常低的。

动态规划法将问题划分为更小的子问题,通过子问题的最优解来重构原问题的最优解。

动态规划法可以存储之前的计算结果,可以极大的提高后面数据的运算效率。

对于一些问题,可以将大问题转为若干个子问题,并利用动态规划思想进行运算可以很大程度上降低运行时间。

实验伪代码

eggDropbao(N,K)	//暴力法 
    attempt = 0,
	breakfloor = N
    for i=K~N
        attempt++
        if i=breakfloor
            return attempt
            
eggDropdp(N,K) //动态规划法  
    for i=1~N //更新dp数组
        for j=1~K 
			if dp[i][j] != INT_MAX
                continue     
            for x=1~i
                attempts = 1 + max(dp[x - 1][j - 1], dp[i - x][j])
                dp[i][j] = min(dp[i][j], attempts) 
			 
    if N > n	//更新n,k值
        n = N
    if K > k
        k = K

    return dp[N][K]
	
	        

实验代码

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <algorithm>
using namespace std;
#define LIMIT 4000      //N,K上限

int **dp;
int n=0, k=0;   //记录已填到的n,k值

void firstsetdp()
{
    dp = new int* [LIMIT + 1];
    for (int i = 0; i <= LIMIT; i++)    //动态创建数组并初始化
    {
        dp[i] = new int[LIMIT + 1];
        for (int j = 0; j <= LIMIT; j++)
            dp[i][j] = INT_MAX;
    }

    for (int i = 0; i <= LIMIT; i++)
    {
        dp[i][0] = 0;  // 0个鸡蛋时,尝试次数为0
        dp[i][1] = i;  // 1个鸡蛋时,逐层尝试
    }

    for (int j = 1; j <= LIMIT; j++)
    {
        dp[0][j] = 0;  // 0层楼时,不需要尝试
        dp[1][j] = 1;  // 1层楼时,不需要尝试
    }
}

int eggDropdp(int N, int K)
{

    //更新dp数组
    for (int i = 1; i <= N; i++) 
        for (int j = 1; j <= K; j++)
        {
            if (dp[i][j] != INT_MAX)
                continue;
            for (int x = 1; x <= i; x++)
            {
                int attempts = 1 + max(dp[x - 1][j - 1], dp[i - x][j]);
                dp[i][j] = min(dp[i][j], attempts);
            }
        }

    //更新n,k值
    if (N > n)
        n = N;
    if (K > k)
        k = K;

    return dp[N][K];
}

int eggDropbao(int N, int K)
{
    int attempt = 0, breakfloor = N;
    for (int i = K; i <= N; i++)
    {
        attempt++;
        if (i == breakfloor)
            return attempt;
    }
}

int main() 
{
    srand(time(0));
    int k = 1;  //记录第几组N,K值
    
    firstsetdp();
    int shi = 0;

    int zushu = 10;
    for(int q=1;q<=zushu;q++)
    {
        // 随机生成楼层数N和鸡蛋个数K
        int N=0, K=0;
        while (N == 0 || K == 0)
        {
            N = rand() % LIMIT;
            if(N!=0)
                K = rand() % N;
        }

        cout << "当前组数为:" << q<< endl;
        cout << "楼层数N:" << N << endl;
        cout << "鸡蛋个数K:" << K << endl;

        int qi = clock();
        int minAttempts;
        //minAttempts = eggDropbao(N, K); //暴力法
        minAttempts = eggDropdp(N, K);  //动态规划法
        int zhi = clock();

        shi += zhi - qi;

        cout << "最少尝试次数:" << minAttempts << endl;
        cout << "耗时:" << zhi - qi << " ms " << endl;
        cout << endl;
    }
    cout << "数据规模:" << LIMIT << endl;
    cout<< "平均耗时:" << shi/zushu << " ms " << endl;
}

(by 归忆)

  • 19
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

归忆_AC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值