今天上quora,看到一个有趣的问题:如何向四岁小朋友(外行)解释动态规划这个术语?如果别人首次听说c程序中的回溯概念,我们应该如何向他们解释这个术语?
下面是google程序员 Jonathan Paulson 的回复,获得了9900+赞
// 拿一张纸,写下 1+1+1+1+1+1+1+1=
“这个等于几?”
(计算中)“八”
(再在左侧补上1+)
“现在又等于几?”
(速度很快)“九”
“为什么你这么快就算到九呀?”
“你只加了一嘛”
“恩,你不需要全部重新计算,因为你记住了前面的结果是八。动态规划就是记住东西以节省后面时间的一种奇妙方法。”斐波那契数列(Fibonacci Sequence)0, 1, 1, 2, 3, 5, 8, 13, 21...可以很容易用递归编程算出第n个数的值:
int fib(int n)
{
//int a=0;b=1;
if(n==0) return 0;
if(n==1) return 1;
return fib(n-1)+fib(n-2);
}但是,这样递归会有大量的重复计算。以f(5)为例,f(5)=f(4)+f(3),而f(4)=f(3)+f(2)。这样,f(3)就被计算了两次。
DP动态编程就是将计算过的数据存储起来以备后用。
unsigned long fib2(int n)
{
map<int,int> m;
m[0]=0;
m[1]=1;
if(m.find(n)!=m.end())
{
return m[n];
}
else
{
return m[n]=fib(n-1)+fib(n-2);
}
}再举一例,0-1背包问题
//0-1 knapsack problem
#include <iostream>
#include <vector>
using namespace std;
#define MAX_ITEMS 10
#define MAX_CAPACITY 100
int knapsack(int value[], int weight[], int n, int C)
{
//For code simplicity, I'm creating a fixed size array.
//Actually it should depend on n and C
int M[MAX_ITEMS+1][MAX_CAPACITY+1] = {{0,},}; //initialize to zero
int i, j;
for(i=1; i<= n; i++)
{
for(j=1; j<=C; j++)
{
if(weight[i-1] > j)
M[i][j] = M[i-1][j];
else
{
M[i][j] = M[i-1][j];
if( (M[i-1][j-weight[i-1]]+value[i-1]) > M[i][j])
M[i][j] = M[i-1][j-weight[i-1]] + value[i-1];
}
}
}
return M[n][C];
}
//This main function is here just to show how to call knapsack()
int main()
{
int value[6] = {3,6,7,9,11,18};
int weight[6] = {1,2,3,5,6,8};
int C = 100;
vector<int> pos;
int w = knapsack(value, weight, 6, 15);
cout << w << endl;
return 0;
}
M(i,j)表示容量为j的背包装前i个物品所取得最优解,1<j<C,1<i<n,M(i,j)的计算如下:
M(i,j)的值有两种可能。
如果第i件物品不装进背包,则 M(i , j) = M(i - 1 , j).
如果第i件物品装进去,M(i , j) = M(i - 1 , j – si) + vi
最终,M(i , j) = max{ M(i - 1 , j) , M(i - 1 , j – si) + vi }
0 if i = 0 or j = 0
M(i , j) = M(i-1, j) if si > j
max{ M(i - 1 , j) , M(i - 1 , j – si) + vi } (otherwise)
最后算得当i=n,j=C即M(n,C)就是所求最优解。
本文通过生动的例子,如计算加法、斐波那契数列和0-1背包问题,介绍了动态规划的基本思想和实现方法。文章展示了如何利用之前计算的结果来避免重复计算,从而提高算法效率。
1222

被折叠的 条评论
为什么被折叠?



