https://skerritt.blog/dynamic-programming/

什么是Python示例动态编程

2019年12月31日·35分钟·蜜蜂
目录

动态编程将问题分解为较小的子问题,解决了每个子问题并将这些子问题的解决方案存储在数组(或类似数据结构)中,因此每个子问题只计算一次。

它既是数学优化方法又是计算机编程方法。

优化问题寻求最大或最小的解决方案。一般规则是,如果遇到在O(2n)时间内解决初始算法的问题,则最好使用动态编程来解决。


为什么将动态编程称为动态编程?

理查德·贝尔曼(Richard Bellman)在1950年代发明了DP。Bellman之所以将其命名为Dynamic Programming,是因为当时RAND(他的雇主)不喜欢数学研究,也不想为其提供资金。他将其命名为“动态编程”,以掩盖他确实在从事数学研究的事实。

Bellman在他的自传《飓风之眼:自传》(1984,第159页)中解释了动态编程一词的原因。他解释说:

“我(1950年)的秋天季度在兰德公司度过。

我的第一个任务是为多阶段决策流程找到一个名称。一个有趣的问题是,动态编程的名称从何而来?

1950年代不是数学研究的好年头。我们在华盛顿有一位非常有趣的绅士,名叫威尔逊。他曾任国防部长,实际上对病理学一词有病态的恐惧和仇恨。我不是在轻率地使用这个词。我正在精确地使用它。

如果人们在他的面前使用“研究”一词,他的脸就会闷闷不乐,他会变成红色,并且会变得暴力。您可以想象他对数学一词的感觉。兰德公司(RAND Corporation)受雇于空军,而空军实质上是由威尔逊(Wilson)担任老板。因此,我觉得我必须做些事情来保护威尔逊和空军免受事实的困扰,因为我实际上是在RAND公司内部从事数学工作。我可以选择什么标题,什么名字?首先,我对计划,决策,思考感兴趣。但是出于各种原因,规划并不是一个好词。因此,我决定使用“编程”一词。我想了解一下这是动态的,这是多阶段的,这是随时间变化的。我想,让我们用一块石头杀死两只鸟。让我们以古典物理意义上具有绝对精确意义的单词(即动态的)为例。它也有一个非常有趣的性质,作为形容词,这是不可能在贬义的意义上使用“动态”一词的。尝试考虑一些可能赋予其贬义性的组合。不可能。因此,我认为动态编程是一个好名字。这是连国会议员都无法反对的东西。因此,我将它用作活动的保护伞。” 这是连国会议员都无法反对的东西。因此,我将它用作活动的保护伞。” 这是连国会议员都无法反对的东西。因此,我将它用作活动的保护伞。”


什么是子问题?

子问题是原始问题的较小版本。让我们来看一个例子。使用以下等式:

1 + 2 + 3 + 41个+2个+3+4

我们可以将其分解为:

1 + 21个+2个

3 + 43+4

一旦解决了这两个较小的问题,就可以将解决方案添加到这些子问题中,以找到整体问题的解决方案。

请注意,这些子问题如何将原始问题分解为构成解决方案的组件。这是一个小例子,但是很好地说明了动态编程的美。如果我们将问题扩展到100的数字上,则更清楚了我们为什么需要动态编程。举个例子:

6 + 5 + 3 + 3 + 2 + 4 + 6 + 56+5+3+3+2个+4+6+5

我们有 6 + 56+5两次。第一次看到它,我们就会锻炼6 + 56+5。当我们第二次看到它时,我们会自省:

“啊,6 +5。我以前见过。是11点!”

在动态编程中,我们存储问题的解决方案,因此我们无需重新计算。通过为每个子问题找到解决方案,我们可以解决原始问题本身。

*备忘*是存储解决方案的行为。


动态编程中的记忆是什么?

让我们来看看为什么存储解决方案的答案才有意义。我们将研究一个著名的问题,斐波那契数列。通常在“分而治之”中解决此问题。

有3个主要部分分而治之:

  1. **将**问题分解为相同类型的较小子问题。
  2. **征服**-递归解决子问题。
  3. **组合**-组合所有子问题以创建原始问题的解决方案。

动态编程在步骤2的基础上又增加了一个步骤。这就是备忘录。

斐波那契数列是一个数字序列。它是最后一个数字+当前数字。我们从1开始。

1 + 0 = 11个+0=1个

1 +1 = 21个+1个=2个

2 +1 = 32个+1个=3

3 + 2 = 53+2个=5

5 + 3 = 85+3=8

在Python中,这是:

def F(n):if n == 0 or n == 1:return nelse:return F(n-1)+F(n-2)

如果您不熟悉递归,那么我为您撰写了一篇博客文章,您应该首先阅读。

让我们计算F(4)。在执行树中,如下所示:

我们计算F(2)两次。在较大的输入(例如F(10))上,重复次数增加。动态编程的目的是避免两次计算相同的事物。

无需两次计算F(2),我们将解存储在某个地方,只计算一次。

我们将解决方案存储在一个数组中。F [2] =1。下面是一些Python代码,用于使用动态编程计算斐波那契数列。

def fibonacciVal(n):memo[0], memo[1] = 0, 1for i in range(2, n+1):memo[i] = memo[i-1] + memo[i-2]return memo[n]


如何识别动态编程问题

从理论上讲,动态编程可以解决所有问题。问题是:

“什么时候应该使用动态编程解决这个问题?”

对于*难以解决的*和*难以解决的*问题之间的问题,我们应该使用动态编程。

可解决的问题是可以在多项式时间内解决的问题。这是说我们可以快速解决问题的一种奇特的方式。二进制搜索和排序都非常快。棘手的问题是那些在指数时间内运行的问题。太慢了 棘手的问题是只能通过对每个单独的组合进行暴力破解才能解决的问题( NP难题)。

当我们看到类似这样的字词时:

“最短/最长,最小/最大,最小/最大,最小/最大,“最大/最小”

我们知道这是一个优化问题。

动态编程算法正确性的证明通常是不言而喻的。其他算法策略通常很难证明是正确的。因此,更容易出错。

当我们看到这些类型的术语时,问题可能会要求一个特定的数字(“查找最小数量的编辑操作”),也可能会要求一个结果(“查找最长的公共子序列”)。后一种类型的问题很难识别为动态编程问题。如果听起来像是优化,则动态编程可以解决问题。

想象一下,我们发现了一个问题,这是一个优化问题,但是我们不确定是否可以使用动态编程来解决。首先,确定我们要优化的内容。一旦意识到要优化的内容,就必须决定进行优化的难易程度。有时候,贪婪的方法足以提供最佳解决方案。

动态编程采用蛮力方法。它确定重复的工作,并消除重复。

在我们甚至开始将问题计划为动态编程问题之前,请考虑一下蛮力解决方案的外观。在蛮力解决方案中是否重复了子步骤?如果是这样,我们尝试将问题想象为动态编程问题。

掌握动态编程就是要了解问题。列出所有可能影响答案的输入。确定了所有输入和输出后,请尝试确定问题是否可以分解为子问题。如果我们可以确定子问题,则可以使用动态编程。

然后,弄清楚什么是复发并解决它。当我们试图找出复发时,请记住,无论我们写出什么复发都必须帮助我们找到答案。有时答案将是重复发生的结果,有时我们必须通过查看重复发生的一些结果来获得结果。

动态编程可以解决许多问题,但这并不意味着没有更有效的解决方案。用动态编程解决问题似乎很神奇,但是请记住,动态编程只是一种聪明的蛮力。有时它能带来很好的回报,有时却仅能起到一点作用。

---

如何使用动态编程解决问题

现在我们已经了解了什么是动态编程以及它通常如何工作。让我们看一下如何为问题创建动态编程解决方案。我们将使用加权间隔调度问题探索动态规划的过程。

假设您是干洗店的所有者。您有n位顾客进来给您洗衣服。您一次只能清洗一个客户的衣服堆(PoC)。每堆衣服i必须在预定的开始时间清洗s_is一世​ 和一些预定的完成时间 _F一世​。

每堆衣服都有相关的价值, v_iv一世​,取决于它对您的业务有多重要。例如,某些顾客可能要花更多钱才能更快地洗衣服。也许有些人回头客,而您希望他们开心。

作为该干洗店的所有者,您必须确定最佳的服装搭配时间表,以使这一天的总价值最大化。该问题是加权间隔调度问题的重新措辞。

现在,您将看到解决动态编程问题的4个步骤。有时,您可以跳过一个步骤。有时,您的问题已经被很好地定义了,您无需担心前几个步骤。

步骤1.写出问题

拿一张纸。写出:

  • 问题是什么?
  • 有哪些子问题?
  • 解决方案大致是什么样的?

在干洗店问题中,让我们将子问题简单化。我们要确定的是每堆衣服的最大值计划,以便按开始时间对衣服进行分类。

为什么按开始时间排序?好问题!我们想跟踪当前正在运行的进程。如果我们按完成时间排序,那对我们来说就没有多大意义了。我们可以有2个具有相似的完成时间,但有不同的开始时间。时间从开始到结束都以线性方式移动。如果我们有成堆的衣服从下午1点开始,我们知道在下午1点穿上衣服。如果我们有一堆衣服要在下午3点结束,那么我们可能需要在下午12点穿好衣服,但是现在是下午1点。

我们可以找到桩的最大价值计划 n-1ñ-1个一直到 然后n-2ñ-2个一直到 等等。通过找到每个子问题的解决方案,我们可以解决原始问题本身。桩号1到n的最大值计划。子问题可以用来解决原始问题,因为它们是原始问题的较小版本。

对于间隔调度问题,我们可以解决的唯一方法是对问题的所有子集进行强行强制,直到找到最佳问题为止。我们要说的是,我们将暴力行为一分为二,而不是一味地蛮力强行。我们从蛮力n-1ñ-1个一直到 然后我们对n-2ñ-2个一直到 最后,我们有许多较小的问题,可以动态解决。我们希望为子问题构建解决方案,以使每个子问题都基于先前的问题。

2.数学递归

我知道,数学很烂。如果您在这里与我裸露,您会发现这并不难。数学递归用于:

定义分治法(动态编程)技术的运行时间

重现也用于定义问题。如果很难将子问题转化为数学,则可能是错误的子问题。

创建数学递归有两个步骤:

1:定义基本情况

基本案例是问题的最小可能面额。

创建重复周期时,请问自己以下问题:

“我在第0步做出什么决定?”

它不必为0。基本情况是问题的最小可能面额。我们通过斐波那契数列看到了这一点。基数是:

  • 如果n == 0或n == 1,则返回1

重要的是要知道基本案例在哪里,这样我们就可以创建递归。对于我们的问题,我们有一个决定要做出:

  • 把那堆衣服洗干净

或者

  • 今天不要洗那堆衣服

如果n为0,也就是说,如果我们有0 PoC,那么我们什么也不做。我们的基本情况是:

如果n == 0,则返回0

2:我在步骤n会做出什么决定?

现在我们知道基本情况是什么,如果我们在步骤n中,我们该怎么做?到目前为止,每堆衣服都符合日程安排。兼容表示开始时间在当前正在清洗的那堆衣服的完成时间之后。该算法有2个选项:

  1. 洗那堆衣服
  2. 不要洗那堆衣服

我们知道在基本情况下会发生什么,以及在其他情况下会发生什么。现在,我们需要找出算法需要向后(或向前)前进的信息。

“如果我的算法在步骤i中,则它需要什么信息来决定在步骤i + 1中该做什么?”

为了在两个选项之间做出决定,算法需要知道下一个兼容的PoC(衣服堆)。给定堆p的下一个兼容PoC是PoC n,使得s_nsñ​ (PoC n的开始时间)发生在 f_pFp​(PoC p的完成时间)。和...之间的不同s_nsñ​ 和 f_pFp​ 应该最小化。

用英语,想象我们有一台洗衣机。我们在13:00放了一堆衣服。我们的下一堆衣服从13:01开始。我们无法打开洗衣机并放入从13:00开始的洗衣机。我们下一堆兼容的衣服是在当前要清洗的衣服的完成时间之后开始的衣服。

“如果我的算法在步骤i中,则它需要什么信息来决定在步骤i-1中该做什么?”

该算法需要了解未来的决策。由PoC in决定是否运行PoC i-1的程序

现在,我们已经回答了这些问题,我们开始在脑海中形成一个反复出现的数学决定。如果没有,那也没关系,随着我们面临更多的问题,编写重复代码变得更加容易。

这是我们的重复周期:

$$ OPT(i)= \ begin {cases} 0,\ quad \ text {如果i = 0} \\ max {v_i + OPT(next [i]),OPT(i + 1)},\ quad \ text {if n> 1} \ end {cases \\ end {cases} $$

让我们详细探讨是什么导致这种数学递归。OPT(i)表示PoC in的最大值计划,以使PoC按开始时间排序。OPT(i)是我们先前的子问题。

我们从基本情况开始。所有复发都需要在某个地方停止。如果调用OPT(0),则返回0。

为了确定OPT(i)的值,有两种选择。我们希望最大程度地利用这些选项来实现我们的目标。我们的目标是为所有成堆衣服提供最大价值计划。一旦选择了在步骤i中获得最大结果的选项我们便将其值记为OPT(i)。

在数学上,两个选项-运行或不运行PoC i,表示为:

v_i + OPT(next [n])v一世​+] )

这表示运行PoC i的决定。它将从PoC i获得的值添加到OPT(next [n]),其中next [n]代表继PoC i之后的下一个兼容服装。当我们将这两个值加在一起时,我们得到了从i到n的最大值调度,因此如果运行,它们将按开始时间排序。

在这里按开始时间排序,因为next [n]是v_i之后的那个,因此默认情况下,它们按开始时间排序。

OPT(i +1)ø Ť +1 )

如果我们决定不运行i,那么我们的值就是OPT(i + 1)。无法获得该值。OPT(i + 1)给出从i + 1到n的最大值计划,以便按开始时间对它们进行排序。

3.确定存储阵列的尺寸及其填充方向

解决动态规划问题的方法是OPT(1)。我们可以将解决方案写为PoC 1到n的最大值计划,以便按开始时间对PoC进行排序。这与“ PoC i至n的最大价值计划”齐头并进。

从步骤2:

OPT(1)= max(v_1 + OPT(next [1]),OPT(2))(1 )=中号一个v1个​+[ 1 ] ),(2 ))

回到前面的斐波那契数,我们的动态编程解决方案依赖于这样的事实,即0到n-1的斐波那契数已经被记忆。也就是说,要找到F(5),我们已经记忆了F(0),F(1),F(2),F(3),F(4)。我们想在这里做同样的事情。

我们遇到的问题是弄清楚如何填写备忘录表。在调度问题中,我们知道OPT(1)依赖于OPT(2)和OPT(next [1])的解决方案。由于排序,PoC 2和next [1]在PoC 1之后具有开始时间。我们需要从OPT(n)到OPT(1)填写我们的备注表。

我们可以看到我们的数组是一维的,从1到n。但是,如果我们看不到可以采用其他方法解决。数组的维数等于OPT(x)所依赖的变量的数量和大小。在我们的算法中,我们有OPT(i)-一个变量i。这意味着我们的数组将是一维的,其大小将为n,因为有n堆衣服。

如果我们知道n = 5,那么我们的记忆数组可能看起来像这样:

memo = [0, OPT(1), OPT(2), OPT(3), OPT(4), OPT(5)]

0也是基本情况。memo [0] = 0,根据我们之前的介绍。

4.编码我们的解决方案

在编写动态编程解决方案时,我喜欢阅读重复并尝试重新创建它。我们的第一步是将数组初始化为大小(n + 1)。在Python中,我们不需要这样做。但是,如果您使用其他语言,则可能需要这样做。

我们的第二步是设置基本情况。

通过工作[i]来寻找利润。我们需要找到与job [i]不冲突的最新工作。想法是使用二进制搜索来找到最新的不冲突的作业。我已经从此处复制了代码,但已对其进行了编辑。

首先,让我们定义什么是“工作”。正如我们所看到的,一份工作由三部分组成:

    # Class to represent a job
class Job: def __init__(self, start, finish, profit): self.start = start self.finish = finish self.profit = profit

开始时间,完成时间以及运行该工作的总利润(收益)。

我们要编程的下一步是时间表。

# The main function that returns the maximum possible
# profit from given array of jobs
def schedule(job): # Sort jobs according to start time job = sorted(job, key = lambda j: j.start) # Create an array to store solutions of subproblems. table[i] # stores the profit for jobs till arr[i] (including arr[i]) n = len(job) table = [0 for _ in range(n)] table[0] = job[0].profit

早先,我们了解到表格是一维的。我们按开始时间对作业进行排序,创建此空表并将表[0]设置为作业[0]的利润。由于我们已经按照开始时间进行了排序,因此第一个兼容的作业始终是job [0]。

我们的下一步是使用我们先前学到的重复记录来填写条目。为了找到下一个兼容的作业,我们使用了二进制搜索。在稍后发布的完整代码中,将包括此内容。现在,让我们担心了解算法。

如果下一个兼容的作业返回-1,则表示索引i之前的所有作业都与其冲突(因此无法使用)。Inclprof表示我们将该项包含在最大值集中。然后将其存储在table [i]中,以便稍后可以再次使用此计算。

    # Fill entries in table[] using recursive property for i in range(1, n): # Find profit including the current job inclProf = job[i].profit l = binarySearch(job, i) if (l != -1): inclProf += table[l]; # Store maximum of including and excluding table[i] = max(inclProf, table[i - 1])

然后,我们的最后一步是将所有项目的利润返还至n-1。

     return table[n-1]

完整的代码如下所示:

    # Python program for weighted job scheduling using Dynamic # Programming and Binary Search # Class to represent a job class Job: def __init__(self, start, finish, profit): self.start = start self.finish = finish self.profit = profit # A Binary Search based function to find the latest job # (before current job) that doesn't conflict with current # job. "index" is index of the current job. This function # returns -1 if all jobs before index conflict with it. def binarySearch(job, start_index): # https://en.wikipedia.org/wiki/Binary_search_algorithm# Initialize 'lo' and 'hi' for Binary Search lo = 0hi = start_index - 1# Perform binary Search iteratively while lo <= hi: mid = (lo + hi) // 2if job[mid].finish <= job[start_index].start: if job[mid + 1].finish <= job[start_index].start: lo = mid + 1else: return mid else: hi = mid - 1return -1# The main function that returns the maximum possible # profit from given array of jobs def schedule(job): # Sort jobs according to start time job = sorted(job, key = lambda j: j.start) # Create an array to store solutions of subproblems. table[i] # stores the profit for jobs till arr[i] (including arr[i]) n = len(job) table = [0 for _ in range(n)] table[0] = job[0].profit; # Fill entries in table[] using recursive property for i in range(1, n): # Find profit including the current job inclProf = job[i].profit l = binarySearch(job, i) if (l != -1): inclProf += table[l]; # Store maximum of including and excluding table[i] = max(inclProf, table[i - 1]) return table[n-1] # Driver code to test above function job = [Job(1, 2, 50), Job(3, 5, 20), Job(6, 19, 100), Job(2, 100, 200)] print("Optimal profit is"), print(schedule(job))

恭喜!just我们已经编写了第一个动态程序!现在我们已经弄湿了,让我们逐步解决另一种类型的动态编程问题。


背包问题

想象你是一个罪犯。呆呆的聪明。您闯入比尔·盖茨的豪宅。哇,好吧!这是几间房?他的洗衣机室比我的整个房子都要大???好吧,该停止分心了。你带了一个小袋子。背包-如果可以的话。

您只能容纳这么多。让我们给它一个任意数字。袋子将支撑重物15,但仅此而已。我们想要做的是最大化我们能赚多少钱,bb

贪婪的方法是挑选可以放入袋子的具有最高价值的物品。让我们尝试一下。我们要偷比尔·盖茨的电视。4000英镑?好的。但是他的电视重15磅。所以……我们带着4000英镑离开。

TV = (£4000, 15)
# (value, weight)

比尔·盖茨(Bill Gates)有很多手表。假设他有2块手表。每只手表重5英镑,每只价值2250英镑。当我们同时偷走这两者时,我们将获得£4500的权重为10的收益。

watch1 = (£2250, 5)
watch2 = (£2250, 5)
watch1 + watch2
>>> (£4500, 10)

在贪婪的方法中,我们不会首先选择这些手表。但是对于我们人类来说,选择具有较高价值的较小物品是有意义的。贪婪方法不能最佳地解决{0,1}背包问题。{0,1}表示我们要么拿走整个物品{1},要么不拿走{0}。但是,动态编程可以最佳地解决{0,1}背包问题。

解决此问题的简单方法是考虑所有项目的所有子集。对于比尔·盖茨的每一个单一组合,我们都会计算出该组合的总重量和价值。

仅那些体重小于 W_ {max}w ^中号一个X​被考虑。然后,我们选择具有最高价值的组合。这是一场灾难!这需要多长时间?比尔·盖茨(Bill Gates)的家很早就回到家了,甚至还不到您的三分之一!在Big O中,此算法需要O(n ^ 2)Ø ñ2个) 时间。

您可以看到我们已经对解决方案和问题有一个大概的了解,而不必将其写下来!


{0,1}背包问题背后的数学

想象一下,我们在比尔·盖茨家中列出了所有物品。我们从一些保险单中偷了它。现在,考虑未来。该问题的最佳解决方案是什么?

我们有一个子集L,这是最佳解决方案。L是S的子集,S包含Bill Gates的所有东西。

我们选择一个随机项目N。L包含N或不包含N。如果不使用N,则该问题的最佳解决方案与{1,2,…,N-1}1 ,2 ,…,ñ-1个。假设Bill Gates的资料按以下顺序排序价值/重量v一个ü ë /瓦特ëħ

假设原始问题的最优值不是子问题的最优值。如果我们有较小问题的次优问题,那么我们就会有一个矛盾-我们应该对整个问题有一个最优问题。

如果L包含N,则该问题的最佳解与 {1,2,3,...,N-1}1 ,2 ,3 ,…,ñ-1个。我们知道项目在其中,因此L已经包含N。为了完成计算,我们将重点放在其余项目上。我们找到了其余项目的最佳解决方案。

但是,我们现在有了一个新的最大允许重量 W_ {max}-W_nw ^中号一个X​-w ^ñ​。如果解决方案中包含项目N,则总重量现在就是最大重量(项目背包中已经带走的物品N)。

这是两种情况。项目N是否处于最佳解决方案中,或者不是。

如果物品N的重量大于 W_ {max}w ^中号一个X​,则无法将其包括在内,因此情况1是唯一的可能性。

为了更好地定义此递归解决方案,让 S_k = {1,2,…,k}小号ķ​=1 ,2 ,…,ķ 和 S_0 = \空集小号0​=∅

令B [k,w]为使用的子集获得的最大总收益S_k小号ķ​。总重量不超过w。

然后我们为每个定义B [0,w] = 0 w \ le W_ {max}ww ^中号一个X​。

那么我们期望的解是B [n, W_ {max}w ^中号一个X​]。

OPT(i)= \ begin {cases} B [k-1,w],\ quad \ text {如果w <} w_k \\ max {B [k-1,w],b_k + B [k-1, w-w_k]},\ \ quad \ text {otherwise} \ end {cases})={ķ-1 ,w ^ ] ,如果w <  wķ中号一个Xķ-1 ,w ^ ] ,bķ​+ķ-1 ,w-wķ​], 否则​

背包问题列表

好吧,拿出一些笔和纸。不完全是。事情将很快变得令人困惑。该备忘录表是二维的。我们有以下项目:

(1, 1), (3, 4), (4, 5), (5, 7)

元组在哪里(weight, value)

我们有2个变量,所以我们的数组是2维的。第一个维度是0到7。第二个维度是值。

而且我们希望权重为7,从而获得最大收益。

  0 1个 2个 3 4 5 6 7
(1,1)                
(4、3)                
(5、4)                
(7、5)                

权重为7。我们从0开始计数。我们将每个元组放在左侧。好的。现在填写表格!

  0 1个 2个 3 4 5 6 7
(1,1) 0              
(4、3) 0              
(5、4) 0              
(7、5) 0              

列是重量。权重为0时,我们的总重量为0。权重为1时,我们的总重量为1。很明显,我知道。但这是一个重要的区分,以后将很有用。

当我们的权重为0时,无论如何我们都无法携带任何东西。0处的所有内容的总权重为0。

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个            
(4、3) 0              
(5、4) 0              
(7、5) 0              

如果我们的总重量是1,那么我们可以拿的最好的物品是(1、1)。当我们遍历此数组时,我们可以容纳更多物品。在(4,3)的行中,我们可以采用(1,1)或(4,3)。但是目前,我们只能取(1,1)。那么,此行的最大收益为1。

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0              
(5、4) 0              
(7、5) 0              

如果我们的总重量是2,则我们能做的最好的是1。每个项目只有1个。我们无法复制项目。因此,无论我们在第1行中的哪个位置,我们都能做到的绝对最佳结果是(1,1)。

现在开始使用(4,3)。如果总重量为1,但(4,3)的重量为3,那么我们直到重量至少为3时,才能拿走该物品。

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个          
(5、4) 0              
(7、5) 0              

现在,权重为3。让我们比较一些东西。我们想要最大的:

MAX(4 + T [0] [0],1)中号(4+[ 0 ] [ 0 ] ,1 )

如果我们位于2、3,则可以从最后一行获取值,也可以使用该行上的项目。我们上一行,然后倒数3(因为此项的权重是3)。

实际上,当我们减去该行上项目的权重时,公式就是剩余的权重。(4,3)的权重为3,而我们的权重为3。3-3 = 03-3=0。因此,我们在T[0][0]T[previous row's number][current total weight - item weight]

MAX(4 + T [0] [0],1)中号(4+[ 0 ] [ 0 ] ,1 )

1是因为前一项。此处的最大值为4。

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4        
(5、4) 0              
(7、5) 0              

最大(4 + t [0] [1],1)中号一个(4+[ 0 ] [ 1 ] ,1 )

总重量为4,商品重量为3。4-3 =1。上一行为0 t[0][1]

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5      
(5、4) 0              
(7、5) 0              

我不会在此行的其余部分中让您感到厌倦,因为没有任何令人兴奋的事情发生。我们有2个项目。而且我们都使用了它们来制作5。由于没有新项目,所以最大值是5。

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0              
(7、5) 0              

进入我们的下一行:

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4        
(7、5) 0              

这是一个小秘密。我们的元组是按重量排序的!这意味着我们可以填充数据的前几行,直到下一个权重点。我们知道4已经是最大值,因此我们可以填写其余部分。这就是备忘录的作用!我们已经有了数据,为什么还要重新计算呢?

我们往上走,向后退4步。这给了我们:

最大(4 + T [2] [0],5)中号一个(4+[ 2 ] [ 0 ] ,5 )。

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4 5      
(7、5) 0              

现在,我们以总重量5计算它。

max(5 + T [2] [1],5)= 6中号一个(5+[ 2 ] [ 1 ] ,5 )=6

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4 5 6    
(7、5) 0              

我们再次做同样的事情:

max(5 + T [2] [2],5)= 6中号一个(5+[ 2 ] [ 2 ] ,5 )=6

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4 5 6 6  
(7、5) 0              

现在,我们的总重量为7。我们选择以下项中的最大值:

max(5 + T [2] [3],5)= max(5 + 4,5)= 9中号一个(5+[ 2 ] [ 3 ] ,5 )=中号一个(5+4 ,5 )=9

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4 5 6 6 9
(7、5) 0              

如果我们的总重量为7,而我们有3个项目(1、3),(4、3),(5、4),则我们可以做的最好的是9。

由于我们的新项目从权重5开始,因此我们可以从上一行进行复制,直到权重为5。

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4 5 6 6 9
(7、5) 0 1个 1个 4 5      

然后,我们再做一次

总重量-新商品的重量。这是5-5 = 05-5=0。我们希望上一行位于位置0。

最大(7 + T [3] [0],6)中号一个(7+[ 3 ] [ 0 ] ,6 )

6代表该总重量的前一行中最好的。

最大值(7 + 0,6)= 7中号一个(7+0 ,6 )=7

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4 5 6 6 9
(7、5) 0 1个 1个 4 5 7    

max(7 + T [3] [1],6)= 8中号一个(7+[ 3 ] [ 1 ] ,6 )=8

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4 5 6 6 9
(7、5) 0 1个 1个 4 5 7 8  

最大(7 + T [3] [2],9)= 9中号一个(7+[ 3 ] [ 2 ] ,9 )=9

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4 5 6 6 9
(7、5) 0 1个 1个 4 5 7 8 9

9是我们可以通过从一组商品中挑选商品而获得的最大值,使得总重量为 \ le 7≤7。

使用动态规划找到{0,1}背包问题的最优集

现在,我们实际上会选择哪些项目来获得最佳设置?我们从以下项目开始:

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4 5 6 6 9
(7、5) 0 1个 1个 4 5 7 8 9

我们想知道9的来源。它是从顶部开始的,因为在第四行9的正上方的数字是9。由于它是从顶部开始的,因此项(7,5)不在最佳集合中使用。

这9来自哪里?

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4 5 6 6 9
(7、5) 0 1个 1个 4 5 7 8 9

这9不是来自其上方的行。**项目(5、4)必须在最佳组合中。**

现在,我们上一行,然后返回4步。(5,4)项的权重为4,因此需要4个步骤。

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4 5 6 6 9
(7、5) 0 1个 1个 4 5 7 8 9

4不是来自上面的行。项目(4,3)必须处于最佳状态。

项目(4,3)的权重为3。我们向上走,向后走3步,到达:

  0 1个 2个 3 4 5 6 7
(1,1) 0 1个 1个 1个 1个 1个 1个 1个
(4、3) 0 1个 1个 4 5 5 5 5
(5、4) 0 1个 1个 4 5 6 6 9
(7、5) 0 1个 1个 4 5 7 8 9

一旦我们达到权重为0的点,就完成了。我们选择的两个项目是(5,4)和(4,3)。总权重为7,总收益为9。我们将这两个元组加在一起即可找出答案。

让我们开始对此进行编码。


使用Python动态编程中的编码{0,1}背包问题

现在我们知道了它的工作原理,并且已经为其得出了递归-对其进行编码应该不会太难。如果我们的二维数组是i(行)和j(列),则我们有:

if j < wt[i]:

如果我们的权重j小于项目i的权重(我对j无贡献),则:

if j < wt[i]:T[i][j] = T[i - 1][j]
else:# weight of i >= jT[i][j] = max(val[i] + t[i - 1][j-wt(i), t[i-1][j])# previous row, subtracting the weight of the item from the total weight or without including ths item

这就是程序的核心功能。我从这里复制了一些代码来帮助解释这一点。我将不解释太多代码,因为除了我已经解释的内容之外,没有更多的内容了。如果您对此感到困惑,请在下面发表评论或给我发送电子邮件

什么是动态编程Python示例相关推荐

  1. python初学者编程指南_动态编程初学者指南

    python初学者编程指南 编程辅导 (PROGRAMMING TUTORIAL) Dynamic programming is an art, the more problems you solve ...

  2. python中使用马尔可夫决策过程(MDP)动态编程来解决最短路径强化学习问题

    在强化学习中,我们有兴趣确定一种最大化获取奖励的策略.最近我们被客户要求撰写关于MDP的研究报告,包括一些图形和统计输出.假设环境是马尔可夫决策过程  (MDP)的理想模型  ,我们可以应用动态编程方 ...

  3. ai人工智能编程_从人工智能动态编程:Q学习

    ai人工智能编程 A failure is not always a mistake, it may simply be the best one can do under the circumsta ...

  4. 如何在编程时屏蔽输入法_取消屏蔽位屏蔽的动态编程

    如何在编程时屏蔽输入法 by Sachin Malhotra 由Sachin Malhotra 取消屏蔽位屏蔽的动态编程 (Unmasking Bitmasked Dynamic Programmin ...

  5. [.NET] 《Effective C#》快速笔记 - C# 中的动态编程

    <Effective C#>快速笔记 - C# 中的动态编程 静态类型和动态类型各有所长,静态类型能够让编译器帮你找出更多的错误,因为编译器能够在编译时进行大部分的检查工作.C# 是一种静 ...

  6. 风变编程的python课程怎么样-高效学风变编程Python,解锁不一样的职场进阶之路...

    导语:今年,很多人在问一个问题:到底什么才是抗风险能力? 今年,很多人在问一个问题:到底什么才是抗风险能力? 稳定的工作?存款?理财? 有人预测:"到2030年,今天一半的工作岗位都将消失. ...

  7. python软件代码示例-python 示例代码1

    第一章 python基础一 ​在此不再赘述为什么学习python这门编程,网上搜索一箩筐.我在此仅说一句python的好,用了你就会爱上它. 本python示例代码1000+带你由浅入深的了解pyth ...

  8. 出现字迹模糊迹象_改变迹象:如何使用动态编程解决竞争性编程问题

    出现字迹模糊迹象 by Sachin Malhotra 由Sachin Malhotra 改变迹象:如何使用动态编程解决竞争性编程问题 (Change the signs: how to use dy ...

  9. python编辑器_没有人比它更懂少儿编程,慧编程Python'吮指编辑器'

    咳咳! 大家好,我是偶尔写文章的康康老师. 今天跟大家介绍的是慧编程家的,睡在Scratch上铺的兄弟--慧编程Python编辑器. 这是一款集才华和颜值为一体的'吮指'编辑器! 忘记肯德基,你的手指 ...

最新文章

  1. 速查笔记(Linux Shell编程上)
  2. Kubernetes——基本概念与理论
  3. 300+Jquery, CSS, MooTools 和 JS的导航菜单资源
  4. java数据类型_JAVA基础篇(数据类型)
  5. Eureka Server 集群
  6. 2021考研初试成绩多少分过线?
  7. SSO CAS 单点系列
  8. 求解偏微分方程开源有限元软件deal.II学习--Step 48
  9. linux文件映射mmap
  10. 网关支付、银联代扣通道、快捷支付、银行卡支付分别是怎么样进行支付的?...
  11. 『CPU飙高』在开发环境中,.NET如何排查CPU飙高原因
  12. 蓝牙 sig base uuid_蓝牙,从系统开机说起
  13. 用文氏图来理解卷积神经网络如何决定提取哪些特征
  14. day_7:代理使用
  15. 数据结构(三)—— 树(1):树与树的表示
  16. 汽车车灯注塑件三维尺寸公差检测
  17. 华为云鲲鹏云服务器系列的规格,#化鲲为鹏,我有话说#使用华为云鲲鹏弹性云服务器部署文字识别Tesseract...
  18. 迷一般的存在:0x7c00
  19. 基于区块链的分布式金融网络
  20. AJAX与JavaScript脚本语言

热门文章

  1. 在IPCAM上实现RTSP协议直播-live555
  2. hyperic hq笔记
  3. iOS直播短视频类APP上架4.3多次终上岸的惨痛经历
  4. 编写应用程序,计算两个非零正整数的最大公约数和最小公倍数,要求两个非零正整数从键盘输入。
  5. JDBC连接到sql server2008
  6. 5G 第五代移动通信系统你知多少?
  7. 手机屏幕 高宽有哪些_苹果手机屏幕有哪些尺寸?
  8. OBS 基础 13 OBS设置项 热键页面梳理
  9. android应用市场汇总
  10. css select默认选中字体颜色,通过js修改input、select默认字体颜色