问题描述:
假设,有一条长度为n的木棍,已知木棍的销售价格Pi与木棍长度i有关,i = 1,2,3,...n.问,怎样切割能获得最大收益。
长度为0的木棍收益肯定是0了,即profit[0] = 0.
切割长度(seg) | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
销售价格(pi) | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
对于长度为n的木棍,他的递推关系是:profit[n] = max(pi[i] + profit[length - seg[i]]), 其中i = 1,2,3,...n;
暴力解决方法:
int Cut_Common(int seg[], int pi[], int arr_len, int source_len)
{
if (source_len == 0)
return 0;
int tmp = -1;
for (int i = 0; i < arr_len; ++i)
{
if (source_len - seg[i] >= 0)
tmp = max(tmp, pi[i] + Cut_Common(seg, pi, arr_len, source_len - seg[i]));
}
return tmp;
}
这样的解法, 会发生多次相同的自问题求解,故效率非常差。联想每次调用产生一个树节点,叶节点代表了结束递归,O(2^n)的时间复杂度
为了解决对相同自问题的多次求解,故可用一个数组储存自问题的解,下次需要时就不用再从新算。
动态规划——自底向上方法:
int _Cut_Dynamic_DownToTop(int seg[], int pi[], int arr_len, int length, int dump[])
{
int tmp;
dump[0] = 0;
for (int i = 1; i <= length; ++i)
{
tmp = -1;
for (int j = 0; j < arr_len; ++j)
{
if (i - seg[j] >= 0)
tmp = max(tmp, pi[j] + dump[i - seg[j]]);
}
dump[i] = tmp;
}
return dump[length];
}
int Cut_Dynamic_DownToTop(int seg[], int pi[], int arr_len, int length)
{
int *dump = (int *)malloc(sizeof(int)*length + 1);
int tmp = _Cut_Dynamic_DownToTop(seg, pi, arr_len, length, dump);
free(dump);
return tmp;
}
解决的主要思路是,先求解长度为1的最大收益,再到2,3.....一直到n,而每次求解的解都储存起来,时间复杂度为O(nm),空间复杂度为O(n)。
自底向上的解法仍有瑕疵,那就是在求解给定问题时,有些较小问题的解常常是不必须的,而自顶向下 + 记忆功能 解决了这个问题。
动态规划——自顶向下(+记忆功能):
int _Cut_Dynamic_TopToDown(int seg[], int pi[], int arr_len, int length, int dump[])
{
if (dump[length] >= 0)
return dump[length];
int tmp = -1;
for (int i = 0; i < arr_len; ++i)
{
if (length - seg[i] >= 0)
tmp = max(tmp, pi[i] + _Cut_Dynamic_TopToDown(seg, pi, arr_len, length-seg[i], dump));
}
dump[length] = tmp;
return dump[length];
}
int Cut_Dynamic_TopToDown(int seg[], int pi[], int arr_len, int length)
{
int *dump = (int *)malloc(sizeof(int)*length+1);
for (int i = 0; i <= length; ++i)
{
dump[i] = -1;
}
dump[0] = 0;
int tmp = _Cut_Dynamic_TopToDown(seg, pi, arr_len, length, dump);
free(dump);
return tmp;
}
注意到,自顶向下 和 暴力解决的 细微差别,自顶向下记录了每一步的结果(这就是记忆功能),而暴力解决方法总是重新算结果。