- 贪心算法(Greed alalgorithm) 是一种在每一步选择中都采起在当前状态下最好或最优(即最有利)的选择,从而但愿致使全局结果是最好或最优的算法。
- 分治算法(Divide and conquer alalgorithm) 字面上的解释是“分而治之”,就是把一个复杂的问题分红两个或更多的相同或类似的子问题,直到最后子问题能够简单的直接求解,原问题的解即子问题的解的合并。
- 动态规划算法(Dynamic programming,DP) 经过将原问题分解为相对简单的子问题的方式来求解复杂问题。一般许多子问题很是类似,为此动态规划法试图仅仅解决每一个子问题一次,从而减小计算量:一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次须要同一个子问题解之时直接查表。
贪心法在处理每一个子问题时,不能回退,而动态规划能够保存以前的结果,择优选择。下面针对Interval Scheduling 问题,分析动态规划在实际问题中的应用。ios
以下图所示,每一个长条方块表明一个工做,总有若干个工做a、b... h,横坐标是时间,方块的起点和终点分别表明这个工做的起始时间和结束时间。算法
当两个工做的工做时间没有交叉,即两个方块不重叠时,表示这两个工做是兼容的(compatible)。编程
当给每一个工做赋权值都为1时,则称为 Unweighted Interval Scheduling 问题;当给每一个工做赋不一样的正权值时,则称为 Weighted Interval Scheduling 问题。ide
问题最终是要找到一个工做子集,集合内全部工做权值之和最大且集合内每一个工做都兼容。测试
对于 Unweighted Interval Scheduling 问题,使用贪心算法便可求解,具体作法是按照结束时间对全部工做进行排序,而后从结束最晚的工做开始,依次排除掉与前一个不兼容的工做,剩下的工做所组成的集合即为所求。优化
然而,对于 Weighted Interval Scheduling 问题,贪心法找到的解可能不是最优的了。此时考虑使用动态规划算法解决问题,兼顾权值选择和兼容关系。ui
一、首先依然按照结束时间对全部的工做进行排序;spa
二、定义p(j)为在工做j以前,且与j兼容的工做的最大标号,经过分析每一个工做的起始时间和结束时间,能够很容易计算出p(j);3d
三、例以下图所示,p(8)=5,由于工做7和6都与8不兼容,工做1到5都与8兼容,而5是其中索引最大的一个,因此p(8)=5。同理,p(7)=3,p(2)=0。code
一、定义opt(j)是j个工做中,所能选择到的最佳方案,即opt(j)是最大的权值和;
二、对于第j个工做,有两种状况:
三、当j=0时,显示结果为0,这是边界条件。
后一步的结果取前一步全部可能状况的最大值,所以综上所述,能获得动态规划的递归关系为:
一、递归法
递归会使得空间复杂度变高,通常不建议使用。
二、自底向上法
从小到大进行计算,这样每次均可以利用前一步计算好的值来计算后一步的值,算法时间复杂度为O(nlogn),其中排序花费O(nlogn),后面的循环花费O(n)。
- 以下图所示,给定一个背包Knapsack,有若干物品Item
- 每一个item有本身的重量weight,对应一个价值value
- 背包的总重量限定为W
- 目标是填充背包,在不超重的状况下,使背包内物品总重量最大。
对于下图的例子,一种常见的贪心思想是:在背包能够装得下的状况下,尽量选择价值更高的物品。那么当背包容量是W=11时,先选择item5,再选择item2,最后只能放下item1,总价值为28+6+1=35。实际上最优解是选择item3和item4,价值18+22=40。这说明了贪心算法对于背包问题的求解可能不是zuiyou的。下面考虑使用动态规划算法求解,首先要推导递归关系式。
相似于Weighted Interval Scheduling问题,定义opt(i, w)表示在有i个item,且背包剩余容量为w时所能获得的最大价值和。
考虑第i个item,有选和不选两种状况:
边界条件: 当i=0时,显然opt(i,w)=0。
后一步的结果取前一步全部可能状况的最大值,所以综上所述,能获得动态规划的递归关系为:
算法迭代过程以下表:
值得注意的是,该算法相对于输入尺寸来讲,不是一个多项式算法,虽然O(nW)看起来很像一个多项式解,背包问题其实是一个NP彻底问题。
为了便于理解,能够写成这种形式:
W在计算机中只是一个数字,以长度logW的空间存储,很是小。可是在实际运算中,随着W的改变,须要计算nW次,这是很是大的(相对于logW来讲)。例如,当W为5kg的时候,以kg为基准单位,须要计算O(5n)次,当W为5t时,仍然以kg为单位,须要计算O(5000n)次,而在计算机中W的变化量相对很小。
给定两个序列x1,x2...xi和y1,y2,...,yj。要匹配这两个序列,使类似度足够大。首先须要定义一个表示代价的量-Edit distance,只有优化使这个量最小,就至关于最大化匹配了这两个序列。
Edit distance的定义以下所示。
其中,匹配到空,设距离为delta,不然字母p和q匹配的距离记为alpha(p,q),若是p=q,则alpha=0;
那么两个序列匹配的总代价为:
设opt(i,j)是序列x1,x2...xi和y1,y2,...,yj之间匹配所花费的最小代价。当i,j不全为0时,则分别有三种状况,分别是xi-gap,yj-gap,xi-yj,分别计算不一样匹配状况所花费的代价,再加上前一步的结果,就能够创建递推关系式,以下所示。
时间和空间复杂度皆为O(mn)。
下面再分析一个具体的编程问题,使用动态规划算法,可是和上面的DP又有一些区别。
有 n 个学生站成一排,每一个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
输入描述
每一个输入包含 1 个测试用例。每一个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每一个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。
输出描述
输出一行表示最大的乘积。
则没法实现“相邻两个学生的位置编号的差不超过 d”的要求。所以,须要定义一个辅助量,来包含对当前学生的定位信息。
其中,j是一个比i小的值,最大为i-1,i、j之差不超过D,f(j,k-1)表示在前j个学生中,选择k-1个学生,且第j个学生必选。f(i,k)选择了第i个学生,f(j,k-1)选择了第j个学生,i、j之差不超过D,这样就能够知足题目要求了。
/********************************************************************* * * Ran Chen <wychencr@163.com> * * Dynamic programming algorithm * *********************************************************************/ #include <iostream> #include <vector> #include <climits> #include <algorithm> using namespace std; int main() { int N, D, K; // 总共N个学生 vector <int> value; while (cin >> N) { for (int i = 0; i < N; ++i) { int v; cin >> v; value.push_back(v); } break; } cin >> K; // 选择K个学生 cin >> D; // 相邻被选择学生的序号差值 // fmax/fmin[i, k]表示在选择第i个数的状况下的最大/小乘积 vector <vector <long long>> fmax(N+1, vector <long long> (K+1)); vector <vector <long long>> fmin(N+1, vector <long long> (K+1)); // 边界条件k=1 for (int i = 1; i <= N; ++i) { fmax[i][1] = value[i - 1]; fmin[i][1] = value[i - 1]; } // 自底向上dp, k>=1 for (int k = 2; k <= K; ++k) { // i >= k for (int i = k; i <= N; ++i) { // 0 <= j <= i-1 && i - j <= D && j >= k-1 long long *max_j = new long long; *max_j = LLONG_MIN; long long *min_j = new long long; *min_j = LLONG_MAX; // f(i, k) = max_j {f(j, k-1) * value(i)} int j = max(i - D, max(k - 1, 1)); for ( ; j <= i - 1; ++j) { *max_j = max(*max_j, max(fmax[j][k - 1] * value[i - 1], fmin[j][k - 1] * value[i - 1])); *min_j = min(*min_j, min(fmax[j][k - 1] * value[i - 1], fmin[j][k - 1] * value[i - 1])); } fmax[i][k] = *max_j; fmin[i][k] = *min_j; delete max_j; delete min_j; } } // opt(N, K) = max_i {f(i, K)}, K <= i <= N long long *temp = new long long; *temp = fmax[K][K]; for (int i = K+1; i <= N; ++i) { *temp = max(*temp, fmax[i][K]); } cout << *temp; delete temp; system("pause"); return 0; }