题目描述
思路分析
解题方法一:递归
设Ipomy走到位置(i,j)所得到的价值之和为value(i,j),那么为了研究问题的方便我们不妨先研究其走到出口时的情况,即假设现在的状态是Ipomy已经走到了出口即处于位置(m-1,n-1),那么由于Ipomy只能向右走或者向下走,所以他的上一个状态要么是在(m-2,n-1)要么是在(m-1,n-2),而要让他走到(m-1,n-1)位置处获得最大价值,那么其走到上一步时获得的价值也应该是最大的,所以此问题又变成了问题规模减小了的求“获得最大价值”的问题,写到这儿,递归思想已经出来了。
程序源代码以及运行结果截图如下,在代码中给出了完整的注释
程序源代码
//夺取宝藏问题
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<malloc.h>
int max(int a, int b); //用于返回两个数中较大的一个;
int max_value(int **array, int m, int n);
int main()
{
int m, n,max_sum;
int** hide; //hide是一个二级指针,是指向指针的指针
while (scanf("%d%d", &m, &n) != EOF)
{
hide = (int**)malloc(sizeof(int*) * m);
for (int i = 0; i < m; i++)
{
hide[i] = (int*)malloc(sizeof(int) * n);
}//为二维数组动态申请内存
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
scanf("%d", &hide[i][j]);
}
}
max_sum = max_value(hide, m - 1, n - 1);
printf("%d\n", max_sum);
for (int i = 0; i < m; i++)
free(hide[i]);
free(hide);
}
return 0;
}
int max(int a, int b)
{
if (a >= b)
return a;
else
return b;
}
int max_value(int **array,int m, int n)
{
if (m == 1 && n == 0)
return array[1][0];
if (m == 0 && n == 1)
return array[0][1]; //递归出口即当其到达(0,1)位置或者(1,0)位置时即为递归出口。
if (m >= 2 && n == 0)
return max_value(array, m - 1, n) + array[m][n];
else if (m == 0 && n >= 2)
return max_value(array, m, n - 1) + array[m][n]; //这里需要说明的是,很显然当到达第一行或者第一列时没有办法继续对行或者列进行减一操作
else
{
return max(max_value(array, m - 1, n) + array[m][n], max_value(array, m, n - 1) + array[m][n]); //取两种路径中获得价值最大的一条路径
}
//算法思路,先考虑ipomy走到最后的出口的前一步是在哪里,由于只能向右或者向下,所以其前一格不是在(m-1,n)就是在(m,n-1),
//若要价值之和最大,那么要求其从第一步到最后一步的前一步的价值之和也是最大的,所以就把问题拆分成了解法相同,问题规模不同的一些子问题,
//可用递归方法求解
}
运行结果截图
但是值得注意的是,由于此题二维数组的大小不确定,所以我们采用动态分配内存的方法,比直接将其定义为一个很大的数组hide[1000][1000]要好的多,直接将其定义为很大的数组会造成空间的浪费,而且需要内存中有那么多连续的存储空间才能申请成功。
动态内存分配的方法,可以先为二维数组的第一维申请内存空间,再分别为每一维申请内存空间
代码如下
hide = (int**)malloc(sizeof(int*) * m); //即先为第一维申请内存空间第一维即二维数组的行,二维数组的行本身是一个一维数组,而一维数组名是指向数组中第一个元素的地址,所以此时返回的是一个二级指针,即指向指针的指针。
for (int i = 0; i < m; i++)
{
hide[i] = (int*)malloc(sizeof(int) * n);
}//为二维数组动态申请内存
算法优化
解题方法二:动态规划法
前面的算法是可行的但是其时间开销很大,运行得到花费的时间为1s多,如下是对上诉算法的优化:
我将要用的数组hide定义成了一个全局数组,那么其是在程序编译的时候就为其分配内存,存储在内存的静态存储区,若将hide定义为局部变量的话其存储方式就是动态存储的,即在程序运行的时候按照需要动态的为其分配内存,也就是位于动态存储区堆栈上面,此时运行程序,会发现程序会报错说:“数组太大导致栈溢出请考虑将一部分变量移出栈”,所以解决定义大数组的一个办法是将这个很大的数组定义为全局变量,另一个解决方法是像我上面一样采用动态申请内存的方法,当然将大数组定义为全局变量要简单一些而且时间花销也要小一点,相当于用空间换时间,而动态分配内存的办法相当于用时间换空间。
在将需要用到的数组定义为全局变量之后,我又定义了一个辅助的数组assist,用于记录走到(i,j)位置时所拿到的最大价值,这样的话只需要两层for循环就可以解出此问题的解,此问题的解就是我定义的辅助数组中最后一个位置上元素的值,这种解法也是用空间换时间
解题方法:动态规划法
解题思路和步骤:
可以使用动态规划算法实现矩阵路径和问题。具体实现步骤如下:
- 创建一个与原矩阵大小相同的二维数组assist。
- 初始化第一行和第一列的值,即assist[0][j]=assist[0][j-1]+hide[0][j]和assist[i][0]=assist[i-1][0]+hide[i][0]。这里需要解释一下,由于他只能向右或者向下走,所以当他位于第一列时他只有一种走法那就是一直向下走,那么他走到位置(i,0)时,其获得的最大价值就是将向下走的路径上的所有价值加起来,同理当他位于第一行时,他也只有一种走法那就是一直向右走,那么它走到位置(0,j)时,所获得的做大价值就是将其向右走时所经过的路径上的所有数字加起来。
- 对于矩阵中剩余的元素,根据递推公式assist[i][j]=max(assist[i-1][j],assist[i][j-1])+hide[i][j]计算assist[i][j]的值。即要走到位置(i,j)处时获得最大价值,那么它走到前一步时获得的价值也是最大的,而它的前一步要么是(i-1,j)要么是(i,j-1)取两者中价值之和最大的一个就行,assist中存放的不就是他走到某个位置所获得的最大价值吗?
- 最终得到assist[m-1][n-1]存储的即为从左上角到右下角的路径中所以数字之和的最大值。
程序源代码
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int hide[1000][1000] = { 0 };
int assist[1000][1000] = { 0 };
int max(int a, int b)
{
if (a > b)
{
return a;
}
else
{
return b;
}
}
int main()
{
int m, n;
while (scanf("%d%d", &m, &n)!=EOF)
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
scanf("%d", &hide[i][j]);
}
}
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
if (i == 0 && j != 0)
{
assist[i][j] = hide[i][j] + assist[i][j-1];
}
else if (j == 0 && i != 0)
{
assist[i][j] = assist[i - 1][j] + hide[i][j];
}
else if (i == 0 && j == 0)
{
continue;
}
else
{
assist[i][j] = max(assist[i - 1][j] + hide[i][j], assist[i][j - 1] + hide[i][j]);
}
}
}
printf("%d\n", assist[m - 1][n - 1]);
}
return 0;
}
运行效果截图
下面这个程序运行时间月为6毫秒,上面的那个程序运行时间约1320毫秒,oj上面规定运行时间不超过1秒所以下面这个算法才可以通过oj的测评