贪心算法和动态规划[zz] http://www.cnblogs.com/asuran/archive/2010/01/26/1656399.html

贪心算法

1.贪心选择性质

所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别在动态规划算法中,每步所作的选择往往依赖于相关子问题的解。因而只有在解出相关子问题后,才能作出选择。而在贪心算法中,仅在当前状态下作出最好选择,即局部最优选择。然后再去解作出这个选择后产生的相应的子问题。贪心算法所作的贪心选择可以依赖于以往所作过的选择,但决不依赖于将来所作的选择,也不依赖于子问题的解。正是由于这种差别,动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常以自顶向下的方式进行,以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为一个规模更小的子问题。

对于一个具体问题,要确定它是否具有贪心选择性质,我们必须证明每一步所作的贪心选择最终导致问题的一个整体最优解。通常可以用我们在证明活动安排问题的贪心选择性质时所采用的方法来证明。首先考察问题的一个整体最优解,并证明可修改这个最优解,使其以贪心选择开始。而且作了贪心选择后,原问题简化为一个规模更小的类似子问题。然后,用数学归纳法证明,通过每一步作贪心选择,最终可得到问题的一个整体最优解。其中,证明贪心选择后的问题简化为规模更小的类似子问题的关键在于利用该问题的最优子结构性质。

2.最优子结构性质

当一个问题的最优解包含着它的子问题的最优解时,称此问题具有最优子结构性质。问题所具有的这个性质是该问题可用动态规划算法或贪心算法求解的一个关键特征。在活动安排问题中,其最优子结构性质表现为:若a是对于正的活动安排问题包含活动1的一个最优解,则相容活动集合a’=a—{1}是对于e’={i∈e:si≥f1}的活动安排问题的一个最优解。

动态规划

动态规划的动机是消除递归过程中产生的大量重叠子问题, 两种方法 : 记忆化搜索 和 自底向上递推.

无后效性,决策只取决于当前状态的特征因素, 而和到达此状态的方式无关.

最优子结构, 在问题转化为子问题时, 原问题最优当且仅当子问题最优.

决策, 状态转移方程就是决策, 对于多阶段的决策问题, 如果具备最优子结构和无后效性, 就可以用动态规划来解决它

多阶段策略问题利用递归的思想, 把规模为n的问题转化为规模为n-1的问题, 直到转化为可以直接求解的原子问题. 一般情况下, 这样的递归方法的时间复杂度是指数级别的, 但是如果所有不同的子问题的数目是多项式级别的, 那么只需要多项式时间就可以解决这个问题, 这就是动态规划的本质. 动态规划算法有三个要素:

1) 所有不同的子问题所组成的表(他包含的问题数目成为问题的大小).

2)问题解决得依赖关系可以看成是一个图.

3) 填充子问题的顺序(实际上是2所得到的图的拓扑排序).

如果子问题数目为O(n^t), 且每个子问题需要依赖O(n^e)个其他子问题, 则称这个问题为tD/eD的.

方程一(1D/1D):最长上升子序列

定义一个实函数w(i, j)(1<=i<j<=n)已知D[0], 状态转移方程为

E[j] = min{D[i] + w(i, j)}, 1<=i<j<=n

其中D[i]可以根据E[i]在常数时间里计算出来.

方程二(2D/0D):最长公共子序列

已知D[i,0]和D[0,j](0<=i, j<=n), 状态转移方程为

E[i, j]=min{D[i-1,j]+xi, D[i,j-1]+yj, D[i-1][j-1]+zii}

其中xi, yj, zij都可以在常数时间里算出来

方程三(2D/1D)

定义实函数w(i, j)(1<=i<j<=n), 已知d[i, i] = 0 (1<=i<=n), 状态转移方程为

C[i, j] = w(i, j) + min{C[i,k-1] + C[k, j]},1<=i<j<=n

方程四(2D/2D)

定义实函数w(i, j)(0<=i<j<=2n), 已知D[i,0]和D[0,j](0<=i, j<=n),状态转移方程为

E[i, j] = min{D[i', j'] + w(i' + j', i + j)}, 1<=i, j<=n

其中D[i, j]可以根据E[i, j]在常数时间里算出来

向前递推和向后递推:递归模型容易算出状态的前趋,适合顺推; 用多阶段决策建立模型, 容易算出状态后继, 适合逆推.

记忆化搜索, 自顶向下递归, 可以避免无用状态, 书写简单, 可以应用剪枝, 但是无法应用滚动数组等优化手段.

recursivefunc(i, j){

if (calculated[i, j]) return memory[i, j];

fill calculated[i, j] and memory[i, j]

return memory[i, j]

}