GoldMine问题其实就是路径选择问题,求解该问题有2中方法,方法1是通过greedy思想,根据当前每一步的最优解,但是只能得到局部最优解,得不到全局最优解;而方法2是通过DP思想从后往前推,假设前n-1步得到的就是n-1步上的最优解,则再加上第n步的最值则就是全局的最优解,所有DP思想可以精确求解全局最优解,这个好像不对,下面才是DP得到全局最优解的根本原因和效率高效的根本原因:
注:DP之所以能得到全局最优解的本质是它对每一种可能的方案都进行了计算,但相较于蛮力法求解的优势在于他存储了中间值,避免了重复计算,减少了计算量,所以D P适用于存在重复计算项的情况。下面回归GoldMine问题本身:
(1)对于n×m的方格,从后往前推算路径;
(2)递推式: f[n][m] = max{ f[n - 1][m - 1], f[n][m - 1], f[n + 1][m - 1] };
(3)终止条件:当m==0时,直接返回第一列的值。
由于初始递归是从最后1列的所有行开始,所以中间存在交叉值,即重复计算项,所以可根据DP思想,通过memorize[n][m]来存储中间值。注每一个f[n][m]值都是左上、左和左下3者中的最大值,因此可实现一个归并排序的比较,根据递推式,算法的主要实现如下:
int DynamicProgramming::GoldMineProblem(std::vector<std::vector<int>>mat)
{
// 对于n×m的方格,从后往前推算路径
//递推式: f[n][m] = max{ f[n - 1][m - 1], f[n][m - 1], f[n + 1][m - 1] };
//终止条件:当m==0时,直接返回第一列的值
//由于初始递归是从最后1列的所有行开始,所以中间存在交叉值,即重复计算项,
int n = mat.size();
int m = 0;
if (n>0)
{
m = mat[0].size();
}
std::vector<std::vector<int>>memorize;
memorize.resize(n);
for (int i = 0; i < memorize.size();i++)
{
memorize[i].resize(m);
}
for (int i = 0; i < memorize.size();i++)
{
for (int j = 0; j < memorize[i].size();j++)
{
memorize[i][j] = -1;
}
}
std::vector<int>num;
for (int i = 0; i < n;i++)
{
num.push_back(conquerGoldMine(mat, memorize, i, m - 1));
}
int result = maxNumber(num);
return result;
}
int DynamicProgramming::conquerGoldMine(std::vector<std::vector<int>>mat, std::vector<std::vector<int>>&memorize, int n, int m)
{
if (memorize[n][m] != -1)
{
return memorize[n][m];
}
int result = 0;
if (m == 0)
{
result = mat[n][m];
}
else
{
std::vector<int>num;
if (m - 1 >= 0)
{
num.push_back(conquerGoldMine(mat, memorize, n, m - 1));
if (n - 1 >= 0)
{
num.push_back(conquerGoldMine(mat, memorize, n - 1, m - 1));
}
if (n + 1 < mat.size())
{
num.push_back(conquerGoldMine(mat, memorize, n + 1, m - 1));
}
}
result = maxNumber(num)+mat[n][m];
}
memorize[n][m] = result;
return result;
}
下面是返回计算1维向量中的最大值的算法:
int DynamicProgramming::maxNumber(std::vector<int>num)
{
//归并排序
mergeSort(num, 0, num.size() - 1);
return num[0]; //返回最大值
}
void DynamicProgramming::mergeSort(std::vector<int>&num, int low, int high)
{
if (low<high)
{
int mid = (low + high) / 2;
mergeSort(num, low, mid);
mergeSort(num, mid + 1, high);
conquerSort(num, low, mid, high);
}
}
void DynamicProgramming::conquerSort(std::vector<int>&num, int low, int mid, int high)
{
int i = low;
int j = mid + 1;
std::vector<int>result;
while (i<=mid&&j<=high)
{
if (num[i]>num[j])
{
result.push_back(num[i]);
i++;
}
else
{
result.push_back(num[j]);
j++;
}
}
while (i<=mid)
{
result.push_back(num[i]);
i++;
}
while (j<=high)
{
result.push_back(num[j]);
j++;
}
for (int k = 0; k < result.size() && low <= high; k++)
{
num[low] = result[k];
low++;
}
}
总结:注意greedy算法和DP思想的区别:greedy算法每一步都是选择当前最优解,没有统计全局的情况,因此最终并不能保证得到全局最优解;而DP是将全局的情况都统计一遍,因此能够得到一个全局最优解,但是DP具有一定的局限性:仅适用于存在重复计算项的问题递推式,如果该问题不存重复计算项,则DP的解法就直接变成了蛮力法。