昨天突然看到dynamic programming,这个是个经典的algorithm,虽然自己在大学和研究生的algorithm课程中都有对这个algorithm的介绍,但无奈自己大学和研究生课堂都属于教室后排的学渣,考试也就蒙混过关了。正好周末,闲来无事,觉得要写点东西记录一下自己积累的一些知识。自己才疏学浅,表达能力有限,肯定有不足的地方,希望看过次blog的读者能指出错误,大家互相学习。好了废话不多说,先介绍我主要参考的一些资料:
- wikipedia,相信大家不会陌生,http://en.wikipedia.org/wiki/Dynamic_programming,内容的英文的。如果英文不太好的同学可以参考中文的wikipedia(其实大家还是最好看英文的材料,毕竟学好英文对于computer science这个专业太重要了,自己也正在努力学习中……)。这是个dynamic programming 的一些基本思想的介绍。自己也没看完,只是看了一部分,总结出了几点对自己有用的几点:(1)dynamic prgramming 和 divide and conquer(分治算法) 的关系,理解这个一点对于理解dynamic programming有很重要的作用,其实两个算法的思想都是将一个复杂的问题进行分解成为多个同样结构的子问题,递归的解决子问题来,然后合并来解决原始问题。先来说divide and conquer 算法,这个算法是将问题分成相互独立的子问题。而对于dynamic programming则分成overlap(重复)的子问题,什么是overlap subprolems ,就是说对于同一个子问题,在递归中会重复的出现,例如对于:fabonacci问题,我们求f(42)=f(41)+f(40),然后在递归的求f(41)=f(40)+f(39)时我们发现f(40)出现了两次,这个就是overlap 问题,往往满足optimal substructure 和overlap subproblems 的问题我们往往能用dynamic programming来解决。既然在递归中的过程中,subproblem会不止一次的出现,那么问题来了,我们可以把这种子问题的solution进行存储,等下次我们在递归过程中遇到此subproblem的时候我们可以不用再用计算它的solution,直接从存储中进行查找,这个对于同样的subproblem我们只进行了一次calculation,这个就大大节约了我们的computation complexity,往往dynamic programming能够将exponential time (指数时间)将为polynomial time 的时间复杂度。而对于divide and conquer 我们的subproblems 直接是独立的(不相同)。其实我们也可以将dynamic programming 中遇到的问题用divide conquer来进行计算,但是我们说了,这往往是指数时间的复杂度,对于input size稍微大一点的问题,runtime将会很大。这也是我们为什么要用到dynamic问题的原因,降低时间复杂度,但是这其实是一个time_memory trade-off(时间和空间的两难选择)。因为我们在用dynamic programming来解决problems时,我们牺牲了memory,我们要用到memory来存储算过的subproplems,但是对于我们空间的牺牲,我们的runtime 降低了很多,所以还是很好的。
- 第二个reference是当然是introduction to algorthms 这本书了,今天还没看完,只看了前几页,对主要算法思想看了一下。其实想说的都在上一个部分说了,对于dynamic programming 问题我们往往有两种methods:top-down with memoization 第二种:bottom-up method ,这两种方法其实可以从字面意思来猜到,我就不多说了,大家可以参考这本书(ps:最好能看英文的)
<span style="font-size:14px;">int memoized_cut_cod(vector<int> &prices, int rod_len,map<int,int> &memo)
{
if (rod_len == 1)
return prices[0];
int revenue = numeric_limits<int>::min();
for (size_t i = 0; i < rod_len-1; i++)
{
int left_opti, right_opti;
map<int, int>::iterator left_iter = memo.find(i + 1), right_iter = memo.find(rod_len - i - 1);
//if both subproblem in memo
if (left_iter != memo.end()&&right_iter!=memo.end())
{
left_opti = left_iter->second;
right_opti = right_iter->second;
int sum_opti = left_opti + right_opti;
revenue = revenue < sum_opti ? sum_opti : revenue;
}
//or just left section are in memo
else if (left_iter != memo.end())
{
left_opti = left_iter->second;
right_opti = memoized_cut_cod(prices, rod_len - i - 1, memo);
memo.insert(make_pair(rod_len - i - 1, right_opti));
int sum_opti = left_opti + right_opti;
revenue = revenue < sum_opti ? sum_opti : revenue;
}
//just right section in memo
else if (right_iter != memo.end())
{
right_opti = right_iter->second;
left_opti = memoized_cut_cod(prices, i + 1, memo);
memo.insert(make_pair(i + 1, left_opti));
int sum_opti = left_opti + right_opti;
revenue = revenue < sum_opti ? sum_opti : revenue;
}
// if both subproblem are not in memo
else
{
//calculate the left section
left_opti = memoized_cut_cod(prices, i + 1, memo);
memo.insert(make_pair(i + 1, left_opti));
//calculate the right section
right_opti = memoized_cut_cod(prices, rod_len - i - 1, memo);
memo.insert(make_pair(rod_len - i - 1, right_opti));
int sum_opti = left_opti + right_opti;
revenue = revenue < sum_opti ? sum_opti : revenue;
}
}
revenue = revenue < prices[rod_len - 1] ? prices[rod_len - 1] : revenue;
return revenue;
}</span><span style="font-size:18px;">
</span>