在这篇文章中,我们将讨论贪婪的方法与动态编程。这两种方法都是用于优化一个给定的问题。一个问题的优化是从一组解决方案中找到最佳解决方案。
一种被称为贪婪算法的算法范式将一个解决方案逐件组装起来,总是选择提供最明显和最直接利益的组件。因此,在选择局部最优解的情况下,"贪婪 "最能产生全局最优解。以小数结包问题为例。根据局部最优的方法,应该选择价值与权重比最高的项目。由于我们被允许对一个项目采取分数的方式,这种技术也会产生全局最优解。动态编程的主要好处是对简单递归的改进。动态编程可以用来优化任何对相同输入进行重复调用的递归方案。为了避免以后不得不重新计算子问题的结果,我们的想法是直接存储它们。通过这种直接的优化,时间复杂度从指数级降低到多项式。例如,如果我们为斐波那契数创建一个直接的递归解决方案,时间复杂度会呈指数级增长;但是,如果我们通过存储子问题的答案来优化它,时间复杂度会下降到线性。
动态编程是可靠的,而贪婪算法却不一定可靠
贪婪算法和动态编程之间的一个重要区别是,前者首先做出一个贪婪的选择,或当时看起来最好的选择,而后者则解决一个随之而来的子问题,而不去解决所有可能相连的更小的子问题。
| 贪婪算法 | 动态编程 |
|---|---|
| 它不保证有最优解 | 它总是能保证一个最优解 |
| 当存在选择解决方案的贪婪方式时,就会出现这样的情况 | 当存在重叠的子问题时,可以使用DP。 |
| 它不考虑未来的选择 | 追踪未来/以前的选择 |
| 所选的选择是局部最优的 | 所选的选择是全局最优的 |
| 例子。分数Knapsack,工作调度,Huffman编码 | 例子。Knapsack问题,矩阵链乘法 |
对于贪婪算法来说,鉴于不需要重新审视或修改早期的解决方案或数值,它的记忆效率很高。贪婪算法在计算解决方案时从不重新访问或修改先前的值或解决方案。
一般来说,它们比动态编程方法更快。
一个例子是O(ELogV + VLogV)-time Dijkstra的最短路径算法。
让我们看看Dijkstra的算法,以了解贪婪方法的情况:
function dijkstra(G, S)
for each vertex V in G
distance[V] <- infinite
previous[V] <- NULL
If V != S, add V to Priority Queue Q
distance[S] <- 0
while Q IS NOT EMPTY
U <- Extract MIN from Q
for each unvisited neighbour V of U
tempDistance <- distance[U] + edge_weight(U, V)
if tempDistance < distance[V]
distance[V] <- tempDistance
previous[V] <- U
return distance[], previous[]
上面是Dijkstra算法的伪代码。该算法用于寻找两个给定节点之间的最短距离。为了确定下一个最佳答案,该算法采取了贪婪的方法,希望最后的结果是整个问题的最佳解决方案。但是,不一定给定的解决方案会是最短的距离。在某一瞬间,Dijkstra的算法会选择一个离起始顶点最近的节点,尽管这可能离目的地顶点更远。这样一来,贪婪的技术可能无法每次都给出最佳解决方案。
在动态编程的情况下,时间上的复杂性从指数级降低到多项式。例如,计算可以将一个递归的解决方案转化为一个动态编程问题。在这个过程中,每个阶段的决策都会考虑到当前的问题以及之前已经解决的和问题的答案。
一个动态编程问题总是有一个最优的解决方案,这是一个既定事实。
在这里,一个全局最优的解决方案被选为最佳选择。它采用了一个公式,该公式将被应用于先前计算的状态值的存储。对动态编程表的记忆是必要的。记忆的复杂性增加了。它的动作相对更慢。一个O(N^3)时间的Floyd-Warshall'salgorithm的例子。
让我们来看看Floyd-Warshall的算法:
n = no of vertices
A = matrix of dimension n*n
for k = 1 to n
for i = 1 to n
for j = 1 to n
Ak[i, j] = min (Ak-1[i, j], Ak-1[i, k] + Ak-1[k, j])
return A
在这里,我们初始化了一个矩阵,跟踪每个节点之间的距离,任何两个节点之间的最短路径在每次迭代时都会更新。这样,在动态编程问题中,就有一个数组或矩阵来存储解决方案,这样就可以在需要的时候获取它(在重叠的子问题中需要)。
贪婪在提供正确答案时更快,因为它只对一个子问题起作用,因此我们不对所有问题都使用动态编程。此外,只有当有重叠的子问题时,动态编程才会给出最优解。
我们如何在贪婪和DP之间进行选择?
如果问题满足贪心选择特性,建议使用贪心方法解决。
如果包含重叠的子问题,则使用动态编程解决。
如果一个问题既能用贪婪技术又能用动态编程解决,该怎么办?
有一些问题,如 "换硬币 "问题,既可以用贪婪的方法,也可以用动态编程范式来解决。在这种情况下,使用贪婪算法可能更好,因为它更快,因为它只解决最优子问题,但动态编程解决所有的子问题。
因此,综上所述,我们知道在哪里使用贪婪方法,在哪里使用动态编程。