钻石金字塔

一、实验题目

矿工从金字塔顶部一直走到金字塔底部,每次只能走左下或者右下方向,每个区域内有一定数量的钻石,求矿工所能开采到的钻石的最大数量

(1)矿工并不事先知道金字塔的钻石分布,但是他可以估算前面两个方块内的钻石数,或者租用探测器来获得前x步内钻石的分布。

(2)又或者,假设他有一张残破的地图

这些情况下的信息量和矿工收益有怎么样的关系呢?

二、实验环境

操作系统:Windows 10 专业版

硬件设备:cpu i5-7200HQ 内存8GB

编码环境:c/c++ Clion

三、实验目的

1、加强对动态规划思想的理解和认识

2、提高问题分析能力,数据处理能力。

四、实验内容及其实验步骤

1、数据生成

使用随机数的方法,采用二维数组存储每个区域内的钻石数量。

使用二维数组存储金字塔,只使用到左上半部分。

每个区域内钻石的数量为0~1000(可自定义)

#include <cstdlib>
#include <ctime>
int map[maxn][maxn]; //地图的大小
void DataGenerate()
{srand((unsigned)time(NULL));int i,j;cin>>n;  // 输入金字塔的深度for(i=1;i<=n;i++){for(j=1;j<=n-i+1;j++){map[i][j]=rand()%1000;cout<<map[i][j]<<" ";}cout<<endl;}
}

令 n = 10,随机生成数据如下

将数组旋转45度,即可得到钻石金字塔,假设矿工目前的位置为(x,y),那么矿工的下一步可以开采 (x+1,y) 或者 (x,y+1)

采用密集分布的方法。

#define K 0.1
void DataDense()
{srand((unsigned)time(NULL));cin>>n;int num = n * K;num = num > 1? num:1;int dia,x,y,i,j;while(num--){x = rand()%n + 1;y = rand()%(n-x-1) + 1;dia = rand() % diamond;for(i=x>2?x-2:1;i<x+2 && i<n;i++)for(j=y>2?y-2:1;j<y+2&&j<n;j++){if(map[i][j]<dia-abs(x+y-i-j))map[i][j] = dia-abs(x+y-i-j);}}/*for(i=1;i<=n;i++){for(j=1;j<=n-i+1;j++){// map[i][j]=rand()%diamond;cout<<map[i][j]<<" ";}cout<<endl;}*/
}

n=20

2、全局动态规划(上帝视角、探测距离为无穷大)

定义二维数组 ans,ans[x][y]表示矿工从出发点到达位置 (x,y)处的最优值,这个最优解是由下面的递推式得到

动态规划: ans[x][y] = max( ans[x-1][y] , ans[x][y-1] ) + map[x][y];

全遍历,动态规划,可以很轻松的得到每一个区域的最优值。

int ans_simple=0;
int ans[maxn][maxn]={0};
int dir[maxn][maxn]={0};
void simple() // God perspective add Dynamic programming
{int i,j;for(i=1;i<=n;i++)for(j=1;j<=n-i+1;j++){ans[i][j]=max(ans[i-1][j],ans[i][j-1]) + map[i][j];/* if(ans[i-1][j]>ans[i][j-1])dir[i][j] = 0; elsedir[i][j] = 1;*/}for(i=1;i<=n;i++){if(ans[i][n-i+1]>ans_simple)ans_simple=ans[i][n-i+1];}cout<<ans_simple<<endl;
}

使用数组存储金字塔从1开始存储,可以方便这一步骤的计算。

例如对于n=3规模

1 3 2

2 1

1

在数组中的存储是

0 0 0 0

0 1 3 2

0 2 1

0 1

ans[0][i]=0 ans[i][0]=0 就是起始条件。

如上代码只得到了最优值,而没有得到最优解

使用一个数组,来存储它的前一个位置,即可得到最优解,最优解代码在注释中有体现。

之后将不在讨论最优解,方法类似,主要来讨论如何求得最优值。

3、探测范围 = 1

下面我们来分析探测范围=1的情况,矿工每次探测一步,即下一步是左下还是右下,如果选择一个方向继续探测,那么另一个方向的未来信息无法得知。

举个例子 n = 3

5 2 4

3 1

1

当前矿工的位置是(1,1),那么对矿工来说地图为

5 2 #

3 #

#

矿工采用贪婪算法,选择眼前能看到的得到的最大值的策略,下一步往下走,不往右走。

最终矿工可以获得的钻石数量为 5 + 3 + 1 = 9

该地图的最优值为 5 + 2 + 4 = 11

这是一个贪婪策略,这种算法并非能找到最优值,但是可以找到矿工眼中看到的最优值,总是寻找局部最优解。

算法思想及其伪代码

curr = map[1][1]; //开始位置
do{探测右边和下边两个方向的钻石数量哪个方向钻石数量多,就移动到该位置
}
while(矿工未到达金字塔底部)

算法如下

void greedy_probe(int x,int y,int curr)
{if(x+y==n+1) //矿工到达了金字塔底部{if(curr > ans_probe_1)ans_probe_1 = curr;return;}map[x+1][y] > map[x][y+1]? greedy_probe(x+1,y,curr+map[x+1[y])                                                     :greedy_probe(x,y+1,curr+map[x][y+1]);
}void probe_1()
{//greedygreedy_probe(1,1,map[1][1]);cout<<ans_probe_1<<endl;
}

4、探测范围 = m

前面两种策略都是对该方法的铺垫,也可以说是m=∞和m=1的特殊情况。

下面我将上面两种算法结合起来,可以得到 自定义探测范围 的解。

从例子出发,给出例子

矿工从金字塔顶 (1,1) 出发,取m = 2,他可以看到的地图是

326 330 519 # #
301 142 # #
872 # #
# #
#

矿工根据当前自己知道的钻石分布情况,来选择下一步该如何走,是向右还是向下。

非常直接,矿工经过思考,它有4条路可以选择

301 -> 872

301 -> 142

330 -> 519

330 -> 142

于是矿工认为向下走,即下一步是301,可以获得更多的钻石数量,而不是选择走330,即使330>301,可以说,矿工的眼光相对更长远了。

于是当前地图为:

326 301 519 # #
301* 142 155 #
872 358 #
124 #
#

标* 的位置,为矿工的当前位置,矿工此时依旧有4条路可以选择

872 -> 124

872 -> 358

142 -> 155

142 -> 358

矿工依旧聪明的选择了下一步为 872

对上述分析作总结

假设矿工的探测范围为m,矿工当前的位置为(x,y)

那么矿工可以看到的金字塔的信息为

(x,y) (x,y+1) (x,y+1) (x,y+2) … (x,y+m)

(x+1,y) (x+1,y+1) … (x+1,y+m-1)

(x+m,y)

至此,可以构造处矿工观察到的局部金字塔

矿工的探测范围是一个边长为m的小的金字塔

矿工的下一步选择只有向右或者向下

如果下一步向右,那么可以有一个m-1规模的金字塔,可以对这个小的金字塔使用全局动态规划,求解出下一步为右情况下,矿工可以期望得到的最多钻石数量。

同理,下一步向下,也可以求得期望得到的最多钻石的数量。

此时矿工需要觉得这一步该怎么走?

策略是:

如果下一步向右走,期望得到的钻石数量更多,就往右走,否则向下走

我们使用两次规模大小为m的全局动态规划,只能决定矿工应该走的下一步该如何选择?是向右还是向下。执行完这一步,那么矿工将面临新的局部金字塔

分析完毕,总结来说,就是 探测范围为m = 全局动态规划 + 探测范围为1,这也启示我们复杂的问题是往往都是简单问题的组合。

注意:矿工如果快要到达金字塔边界时,探测范围为m可能已经超出了范围,此时我们取边界值即可。

int dynamic(int x,int y,int steps) //矿工当前位置为(x,y),可以看到的规模是steps
{fill(arr[0],arr[0]+maxn*maxn,0);int i,j;int res=0;int right = n-x+1;if( x + steps > right )steps = right -x;for(i=x;i<=x+steps;i++){for(j=y;i+j<=x+y+steps;j++){arr[i][j]=max(arr[i-1][j],arr[i][j-1]) + map[i][j];}}for(i=x;i<=x+steps;i++)if(arr[i][steps-i+y+x]>res){res = arr[i][steps-i+x+y];}return res;
}
void probe_go(int x,int y,int &res,int m)
{int a=0,b=0;if( x+1 <= n && y <= n && x+y <= n+1) // right{a = dynamic(x+1,y,m-1);}if( x <= n && y+1 <= n && x+y <= n+1 ) // down{b = dynamic(x,y+1,m-1);}if(a==0&&b==0)return;if(a > b) //根据两个方向的期望值,来决定下一步向哪个方向走{res+=map[x+1][y];probe_go(x+1,y,res,m);}else{res+=map[x][y+1];probe_go(x,y+1,res,m);}}
void probe_m() // predict m steps for diamonds
{int m;int res=0;cin>>m;res = map[1][1];probe_go(1,1,res,m);cout<<res<<endl;/*x y is the current position of the miner,he can predict m steps so that he choose the best next step */
}

四、实验数据分析

设金字塔的规模为N,

全局动态规划很明显就是两层for循环,时间复杂度为O(N2)

探测范围为m,矿工总共需要移动N-1次(不包括出发点)到达金字塔底部,上面分析已经知道,矿工每移动一次,需要计算的规模时O(M2),因此总的时间复杂度为O(N×M2)

测试代码如下,计算10次求平均值

void probe_m() // predict m steps for diamonds
{int m[]={1,2,5,10,20,40};int res=0,r;// cin>>m;int i,j;//  res = map[1][1];for(i=0;i<6;i++){r = 0;for(j=0;j<10;j++){res=map[1][1];probe_go(1,1,res,m[i]);r+=res/10;}cout<< m[i] << ":" << r<<endl;}
}
m\N 100 200 500 1000
1 64180|67830 136240 329183 647560
2 68080|70520 132590 337477 688859
5 68310|73830 143800 353247 713960
10 69590|71910 144780 357894 719028
20 73000|74390 141730 356911 713875
40 62620|75020 147430 363764 731622
N(最优解) 73867|75026 147431 368527 1244154

当N=500,1000的时候,计算机计算已经非常吃力,我只随机的计算了一次作为结果。

可以从数据结果中粗略看到,当m逐渐增大时,计算出的结果往往会向最优解靠近(但不绝对)。计算时间和计算结果之间的关系也非常有意思。

当N远大于m时,贪婪算法难以得到理想的结果,见N=1000情况。

而N的其他三个值,虽然随着m的逐渐增大,计算的结果可能越接近最优值,但是付出的时间代价要多的多

例如 N = 100,m=5 取值73830,m=40 取值 75020

m=5  计算量 5*5*100
m=40 计算量 40*40*400

理论上,m=40是m=50花费时间的 (40/5)2=64倍,而计算精确度差了多少呢?

73830 / 75026 × 100% = 98.4%

75020 / 75026 × 100% = 99.99%

有力的体现了,贪心往往无法达到最优值,但是可以在时间复杂度低得多的情况下,取得近似最优值

五、实验总结

本次实验融合了动态规划和贪心策略的知识,加强了对两种算法思想的理解与认识,体会了算法策略的由浅入深的

突破策略,复杂的问题可以通过简单的问题的组合来解决。

实验耗费时间4个小时,完全独立完成,感到有了很大收获。

算法——钻石金字塔(动态规划+贪心)相关推荐

  1. 信息学奥赛一本通(C++版) 第二部分 基础算法 第九章 动态规划

    总目录详见:https://blog.csdn.net/mrcrack/article/details/86501716 信息学奥赛一本通(C++版) 第二部分 基础算法 第九章 动态规划 第一节 动 ...

  2. 疯子的算法总结(四)贪心算法

    一.贪心算法 解决最优化问题的算法一般包含一系列的步骤,每一步都有若干的选择.对于很多最优化问题,只需要采用简单的贪心算法就可以解决,而不需要采用动态规划方法.贪心算法使所做的局部选择看起来都是当前最 ...

  3. 【数据结构与算法】【算法思想】动态规划

    贪心算法 回溯算法 分治算法 动态规划 贪心:一条路走到黑,就一次机会,只能哪边看着顺眼走哪边 回溯:一条路走到黑,无数次重来的机会,还怕我走不出来 (Snapshot View) 动态规划:拥有上帝 ...

  4. 算法模板:动态规划之线性DP【沈七】

    算法模板:动态规划之线性DP 前言 线性DP 数字三角形模型 摘花生 最小路径和 不同路径模型 不同路径(有障碍) 过河卒 (综合应用) 最长上升子序列模型 木棍加工 导弹拦截 完结散花 参考文献 前 ...

  5. 算法——最优解之动态规划

    相应的练习代码:https://github.com/liuxuan320/Algorithm_Exercises 1. 动态规划的定义 动态规划作为一个非常优秀的算法被很多应用称为Optimal A ...

  6. _28LeetCode代码随想录算法训练营第二十八天-贪心算法 | 122.买卖股票的最佳时机II 、55.跳跃游戏、45.跳跃游戏II

    _28LeetCode代码随想录算法训练营第二十八天-贪心算法 | 122.买卖股票的最佳时机II .55.跳跃游戏.45.跳跃游戏II 题目列表 122.买卖股票的最佳时机II 55.跳跃游戏 45 ...

  7. 算法——人的天性贪心算法

    相应的练习代码:https://github.com/liuxuan320/Algorithm_Exercises 0. 写在前面 说起贪心算法,可能是我们最为熟悉的算法了.正如我标题所说的,贪心算法 ...

  8. _32LeetCode代码随想录算法训练营第三十二天-贪心算法 | 738.单调递增的数字 、714.买卖股票的最佳时机含手续费、968.监控二叉树

    _32LeetCode代码随想录算法训练营第三十二天-贪心算法 | 738.单调递增的数字 .714.买卖股票的最佳时机含手续费.968.监控二叉树 题目列表 738.单调递增的数字 714.买卖股票 ...

  9. java贪心算法 区间调度_贪心算法-区间调度问题解之证明(示例代码)

    一.贪心算法 定义:一个算法是贪心算法,如果它是通过一些小的步骤来一个求解,并且在每一步根据局部情况选择一个决定,使得某些主要的指标得到优化. 二.区间调度问题 1. 问题:我们有一组需求{1,2,3 ...

最新文章

  1. LeetCode简单题之删除一个元素使数组严格递增
  2. 【转】文件恢复神器extundelete
  3. Word2010去除灰色中括号标记
  4. telegram 组(groups) 和 频道(channels) 简介
  5. 第九章 思科竞争谋略
  6. idea 查看jsp是否被引用_IntelliJ IDEA解析JSP中的Web路径
  7. macos可以升级到指定版本吗_iOS14如期而至!重大更新的全新版本,值得升级吗?答案在这...
  8. Hibernate基本概念 (2)
  9. Seq(HDU-6672)
  10. 热加载beetl模板
  11. WEB开发新势力——Openparty
  12. Mac OS X从10.7升级到Mountain Lion OS X10.8
  13. 2019上半年系统集成项目管理工程师下午真题及答案解析
  14. 安卓仿苹果音量调节_安卓不仿苹果静音键?千万别小瞧“静音键”, 功能强悍到无敌!...
  15. ios重签名工具ios-app-signer的使用
  16. 天行健,君子以自强不息;地势坤,君子以厚德载物的解释
  17. Cadence: 各软件业务
  18. python爬取网页停止_如何使用Python抓取雪球网页?
  19. w7系统计算机搜索无法搜索了,win7系统在搜索框中输入文字后无法搜索的详细教程...
  20. HTML figcaption 标签

热门文章

  1. 记一次前后端分离开发部署经历
  2. 当n趋向于无限大时,n的0.1次方和logn谁大
  3. 《Adobe Illustrator CS6中文版经典教程(彩色版)》—第1课1.10节使用视图命令
  4. 0-180度移相电路设计
  5. Win7视觉效果设置详解
  6. win10提示无法在驱动器0分区上安装windows解决方法
  7. 《面试八股文》之GitHub中文社区Java 领域又一份备战神器,开冲金三银四
  8. 【Spring Data JPA】基于 JpaRepository 增删改查
  9. 在PyBullet中进行机械臂的强化学习
  10. c语言零件匹配,机械零件设计的C语言编程.pdf