有障碍物的不同路径数量
实验目的
(1)理解动态规划算法的求解过程。
(2)分析动态规划算法的时间复杂度,比较动态规划算法与其他算法的效率差异。
(3)学会如何利用动态规划算法求解具体问题,了解动态规划算法的局限性。
实验任务
(1)完成实验5.3(有障碍物的不同路径数量)的各项要求。
(2)用C++语言实现该算法并进行测试。
(3)撰写实验报告,实验报告内容包括实验目的、实验任务、实验环境、实验步骤、实验结果和实验总结等部分。
实验步骤及结果
实验预习
从左下角以最短距离到达 B 点的路径数为多少?
X1 + X2
从左下角以最短距离到达 D 点的路径数为多少?
Y1 + Y2
写出采用动态规划法求解该问题的递推方程和边界条件(简要描述dp数组、数组下标和递推方程的含义)
构建三维dp数组:dp[i][j][k]
dp数组下标含义:1. i代表横坐标 2. j代表纵坐标 3. k为0或1,0代表最短路径,1代表最短路径数
dp数组含义:从起点到达坐标为(i, j)点的最短路径和最短路径数
注:dp数组中坐标不是以题目中说明的左下角为原点,而是以数组中的(0, 0)为原点,可以在代码中看到一些坐标转换部分。
递推方程:只有不在大厦里面的点才能应用递推方程,而大厦周围的点可以应用递推方程
对于dp[i][j][0]和dp[i][j][1]有三种情况:
1.(i, j)的左边的点和下面的点都不在大厦内
则递推公式为:
如果dp[i+1][j][0] == dp[i][j-1][0]
则dp[i][j][0] = dp[i+1][j][0] + 1
dp[i][j][1] = dp[i+1][j][1] + dp[i][j-1][1]
如果dp[i+1][j][0] > dp[i][j-1][0]
则dp[i][j][0] = dp[i+1][j][0] + 1
dp[i][j][1] = dp[i+1][j][1]
如果dp[i+1][j][0] < dp[i][j-1][0]
则dp[i][j][0] = dp[i][j-1][0] + 1
dp[i][j][1] = dp[i][j-1][1]
2.(i, j)的下面的点不在大厦内
则递推公式为:
dp[i][j][0] = dp[i+1][j][0] + 1
dp[i][j][1] = dp[i+1][j][1]
3.(i, j)的左边的点不在大厦内
则递推公式为:
dp[i][j][0] = dp[i][j-1][0] + 1
dp[i][j][1] = dp[i][j-1][1]
对于边界条件:
在最左边和最下面的坐标的最短路径是起点到此坐标的长度,最短路径数是1,即有
dp[i][0][0] = R – i ,dp[i][0][1] = 1 , i = 0…R
dp[R][j][0] = j , dp[R][j][1] = 1 , j = 0…C
从左下角以最短路径到达 C1和C2 点的路径数是多少?
C1->6
C2->35
用C/C++语言实现该算法的源代码
#include <iostream>
using namespace std;
// 此代码坐标原点以数组(0, 0)为原点
int R; // 行数
int C; // 列数
int n; // 大厦数
int num = 0; // 数据组数
int dp[101][101][2]; // dp数组 含义在报告中已说明
int edifice[101][4]; // 大厦四个角的坐标
int res[101]; // 存储每组数据的结果
// 判断坐标是否在大厦内
bool is_ok1(int i, int j)
{
// 如果在大厦内 返回false
for (int k = 0; k < n; ++k)
{
if (i > edifice[k][0] && i < edifice[k][1] && j > edifice[k][2] && j < edifice[k][3])
{
return false;
}
}
return true;
}
// 判断从(i1, j1) 到 (i2, j2) 是否合法
bool is_ok2(int i1, int j1, int i2, int j2){
// 如果(i1, j1) 在大厦内
if(!is_ok1(i1, j1)){
return false;
}
// 从左边到达
if(i1 == i2){
for(int i = 0;i < n;++i){
if(i1 > edifice[i][0] && i1 < edifice[i][1] && j2 == edifice[i][3] && j1 == edifice[i][2]){
return false;
}
}
return true;
}
// 从下面到达
if(j1 == j2){
for(int i = 0;i < n;++i){
if(j1 > edifice[i][2] && i1 < edifice[i][3] && i2 == edifice[i][0] && i1 == edifice[i][1]){
return false;
}
}
return true;
}
return false;
}
int main(void)
{
while (cin >> R >> C)
{
// 如果为0 0 则退出
if (R == 0 && C == 0)
{
break;
}
cin >> n;
// 读取每个大厦的坐标
for (int i = 0; i < n; ++i)
{
int x, y, z, b;
cin >> x >> y >> z >> b;
// 存储大厦坐标 这里有对输入坐标转换为以(0, 0)为原点的坐标
edifice[i][2] = y - 1;
edifice[i][3] = edifice[i][2] + b;
edifice[i][1] = R - x + 1;
edifice[i][0] = edifice[i][1] - z;
}
// 初始化dp数组
for (int i = 0; i <= R; ++i)
{
dp[i][0][0] = R - i;
dp[i][0][1] = 1;
}
for (int j = 0; j <= C; ++j)
{
dp[R][j][0] = j;
dp[R][j][1] = 1;
}
// 由下到上 由左到右遍历dp数组
for (int i = R - 1; i >= 0; --i)
{
for (int j = 1; j <= C; ++j)
{
// 如果坐标不在大厦内
if (is_ok1(i, j))
{
// 三种情况的递推公式
if (is_ok2(i + 1, j, i, j) && is_ok2(i, j - 1, i, j))
{
if (dp[i + 1][j][0] == dp[i][j - 1][0])
{
dp[i][j][0] = dp[i + 1][j][0] + 1;
dp[i][j][1] = dp[i + 1][j][1] + dp[i][j - 1][1];
}
else if (dp[i + 1][j][0] > dp[i][j - 1][0])
{
dp[i][j][0] = dp[i + 1][j][0] + 1;
dp[i][j][1] = dp[i + 1][j][1];
}
else
{
dp[i][j][0] = dp[i][j - 1][0] + 1;
dp[i][j][1] = dp[i][j - 1][1];
}
}
else if (is_ok2(i + 1, j, i, j))
{
dp[i][j][0] = dp[i + 1][j][0] + 1;
dp[i][j][1] = dp[i + 1][j][1];
}
else
{
dp[i][j][0] = dp[i][j - 1][0] + 1;
dp[i][j][1] = dp[i][j - 1][1];
}
}
}
}
// 将结果存储对应的res数组中
res[num++] = dp[0][C][1];
}
// 输出结果
for(int i = 0;i < num;++i){
cout<<res[i]<<endl;
}
system("pause");
return 0;
}
分析上述算法的时间复杂度,给出复杂度结果
上机实验
上机调试,利用实验教程上的测试用例验证程序是否正确
与实验教程中输出结果一致,验证正确
设计新的测试用例,对程序进行进一步验证
测试用例:
2 3
1
1 2 2 1
0 0
测试用例示意图:
手动推导结果:
其中括号中第一个数代表到达此点的最短路径,第二个数代表到达此点的最短路径数。
程序运行结果:
与手动推导结果相同。
实验总结
理解了动态规划算法的求解过程,学会了分析动态规划算法的时间复杂度和比较了动态规划算法与其他算法的效率差异。学会了如何利用动态规划算法求解具体问题,了解了动态规划算法的局限性。