动态编程:我有重叠的子问题吗?

时间:2018-10-11 10:32:05

标签: algorithm recursion dynamic-programming

我的算法

让我们假设我有一个二维实数数组。我从这个数组中的一个特定单元格开始,其中有一个特别大的数目。我要标记其他哪些单元格应该属于上述起始单元格。规则是这样的:如果我找到一种从起始单元格转到另一个单元格的方式,则另一个单元格属于起始单元格。我只被允许向上或向下行走一个牢房。只允许我从人数较多的牢房步行到人数较少的牢房。这是当我从第9个中心开始时的示例

enter image description here

我的伪算法是

function Step(cellNr):
    foreach neighborNr in neighbors_of(cellNr):
        if array_value(neighborNr) < array_value(cellNr):
            mark_cell(neighborNr)
            Step(neighborNr)
Step(centerNr)

现在是第二个方面,例如,我不仅对一个起始单元格执行此操作,还对多个起始单元格进行操作

enter image description here

动态编程

我研究了dynamic programming,发现要应用动态编程需要满足两个条件:

  • 子问题需要重叠
  • 子问题需要具有最佳的子结构

“ [动态编程]指的是通过以递归的方式将其分解为较简单的子问题来简化复杂的问题。[...]如果可以通过将其分解为子问题来最佳地解决问题,然后进行递归查找, daccess-ods.un.org daccess-ods.un.org子问题的最优解,则被认为具有最优子结构。为了使动态编程适用,问题必须具有两个关键属性:最优子结构和重叠子问题。如果通过组合非重叠子问题的最优解可以解决问题,则该策略称为“分而治之”,这就是为什么合并排序和快速排序不属于动态规划问题的原因。特定子问题的最优解可以通过子问题的最优解的组合来获得,这种最优子结构通常通过递归来描述。子问题的重叠意味着子问题的空间必须很小,也就是说,任何解决该问题的递归算法都应该一遍又一遍地解决相同的子问题,而不是生成新的子问题。“ Wikipedia

我想知道我的算法是否是动态编程。它绝对是递归的,在子结构中似乎是最佳的。我开始怀疑重叠的子结构。有一个有关斐波那契数的例子,但在我看来,关键的方面是可以存储递归算法的中间结果。对于我的算法,无法存储中间结果-至少一次运行单个起始单元格不会。但是,当我考虑整个问题时,如果有很多起始单元,我们会发现某些区域已连接:

enter image description here

让我们说我们从左图中的橙色9开始,然后沿着绿色路径前进,直到到达蓝色5。从那里我们也可以到达蓝色3和蓝色2。我们完成了左侧的算法橙色9。

现在,我们转到右图中较低的橙色8。我们从这个8开始,然后沿着绿色的路径到达绿色6。从那里我们到达蓝色5。从前面的计算(从左图中的橙色9)我们已经知道蓝色3和蓝色2可以从蓝色5到达,因此我们可以一键标记它们,而无需重新计算路径。

这就是为什么我认为我的整体问题可以通过动态编程解决。

问题

  1. 我的算法/问题是动态编程吗?为什么,为什么不呢?
  2. 如果不是,我可以使其成为动态编程吗?如果可以,怎么做?

1 个答案:

答案 0 :(得分:1)

是的,这肯定是一个动态编程问题。实际上,这是最简单/最基本的动态编程问题-在有向无环图(在您的情况下为多个起始节点)中找到从起始节点可到达的所有节点。您可以通过深度优先搜索或宽度优先搜索来解决它。

它符合这样的定义:

最佳结构?是的,我可以从一个单元格x到达的单元格是x加上我可以从x的较小邻居到达的单元格的并集。

子问题重叠?是的,x的两个邻居可以共享同一个较小的邻居。

为了使发布的算法成为动态编程算法,您只需记住以下子问题:

def f(a, b=0):
    print (a*b)

a = 1
interact(f, a, b=(0,10,1))

请注意,这还会将您的算法从指数时间更改为线性时间,并且它是深度优先搜索