标签:
动态规划(dynamic programming)类似于分治法,都是通过组合子问题的解来求解原问题。不同的是,分治法将原问题划分为互不重叠的子问题,递归的求解,在将子问题的解合并,从而求解原问题的解。动态规划则通常应用用子问题重叠的情况,即不同的子问题具有公共的子子问题。
动态规划通常用于求解最优化问题(optimization problem)。一般情况下,这类问题可以有多个可行解,其中具有最优值的解称为一个最优解。
动态规划算法主要包含以下四个步骤:
1. 刻画一个最优解的结构特征;
2. 递归的定义最优解;
3. 计算最优解的值,通常采用自底向上的方法;
4. 利用计算出的信息构造一个最优解。
在上述步骤中,步骤1~3是动态规划算法的基础,如果仅仅需要一个最优解的值,而非最优解本身,可以忽略步骤4。若需要给出最优解本身,通常需要在步骤3中维护一些额外信息,从而构造一个最优解。
动态规划的思想是仔细的安排子问题的求解顺序,保证对每个子问题只求解一次,并将结果保存下来,若后续步骤需要此子问题的解时,只需要通过查找保存的结果,而不必重新计算。
动态规划方法是典型的时空权衡(time-memory trade-off)的例子,即通过付出额外的内存空间来节省计算时间。这种时间上的节省通常是将一个指数时间的解转换为一个多项式时间的解。
动态规划包含两种实现方式:带备忘的自顶向下法(top-down with memoization)和自底向上法(bottom-up method)。
1. 带备忘录的自顶向下法:该方法按照自然的递归形式编写过程,但该过程会保存每个子问题的解。当需要求解一个子问题时,首先检测是否已经保存过此解。如果是,则直接返回保存的值,从而节省了计算时间;否则按照通常方式计算这个子问题。
2. 自底向上法:这种方法一般需要恰当定义子问题“规模“的概念,使得任何子问题的求解都只依赖于”更小的“子问题的求解。因而可以将子问题按规模排序,按由小到大的顺序进行求解。当求解某个子问题时,它所依赖的那些更小的子问题都已求解完毕,结果已经保存。
下面给出了菲波那切数列的三种计算方式,第一种采用递归的方式,后续两种采用动态规划的两种不同实现方式。
#include <stdio.h>
#include <time.h>
const static int UNKNOWN = -1;
static int knownF[100];
int F1(int n)
{
if (n < 1) return 0;
if (n == 1) return 1;
return F1(n-1) + F1(n-2);
}
int F2(int n)
{
int t;
if (knownF[n] != UNKNOWN) return knownF[n];
t = F2(n-1) + F2(n-2);
return knownF[n] = t;
}
int F3(int n)
{
int prev1 = 0, prev2 = 1, tmp, i;
for (i = 0; i < n; i++) {
tmp = prev2;
prev2 += prev1;
prev1 = tmp;
}
return prev1;
}
int main()
{
int i;
time_t s;
for (i = 0; i < 100; i++) {
knownF[i] = UNKNOWN;
}
knownF[0] = 0, knownF[1] = 1;
s = time(NULL);
i = F1(45);
printf("F1: %d, times: %ld\n", i, time(NULL) - s);
s = time(NULL);
i = F2(45);
printf("F2: %d, times: %ld\n", i, time(NULL) - s);
s = time(NULL);
i = F3(45);
printf("F3: %d, times: %ld\n", i, time(NULL) - s);
return 0;
}
[1] 《算法导论》
标签:
原文地址:http://blog.csdn.net/japinli/article/details/51356223