分而治之算法和动态规划算法有什么区别?这两个术语有何不同?我不了解他们之间的区别。
请举一个简单的例子来说明两者之间的区别,以及它们看起来相似的依据。
分而治之算法和动态规划算法有什么区别?这两个术语有何不同?我不了解他们之间的区别。
请举一个简单的例子来说明两者之间的区别,以及它们看起来相似的依据。
Answers:
分而治之
分而治之的工作原理是将问题划分为多个子问题,然后递归地解决每个子问题,然后将这些解决方案组合在一起。
动态编程
动态编程是一种用于解决子问题重叠的技术。每个子问题仅解决一次,并且每个子问题的结果存储在表中(通常实现为数组或哈希表),以备将来参考。这些子解决方案可用于获取原始解决方案,并且存储子问题解决方案的技术称为备忘录。
您可能会想到 DP = recursion + re-use
理解差异的经典示例是查看这两种获取第n个斐波那契数的方法。从麻省理工学院检查该材料。
分而治之的方法
动态规划方法
分而治之与动态编程之间的另一个区别可能是:
分而治之:
动态编程:
有时在递归编程时,可以多次调用具有相同参数的函数,这是不必要的。
著名的斐波那契数示例:
index: 1,2,3,4,5,6...
Fibonacci number: 1,1,2,3,5,8...
function F(n) {
if (n < 3)
return 1
else
return F(n-1) + F(n-2)
}
让我们运行F(5):
F(5) = F(4) + F(3)
= {F(3)+F(2)} + {F(2)+F(1)}
= {[F(2)+F(1)]+1} + {1+1}
= 1+1+1+1+1
所以我们称:1倍F(4)2倍F(3)3倍F(2)2倍F(1)
动态编程方法:如果您多次调用具有相同参数的函数,请将结果保存到变量中以在下一次直接访问它。迭代方式:
if (n==1 || n==2)
return 1
else
f1=1, f2=1
for i=3 to n
f = f1 + f2
f1 = f2
f2 = f
让我们再次调用F(5):
fibo1 = 1
fibo2 = 1
fibo3 = (fibo1 + fibo2) = 1 + 1 = 2
fibo4 = (fibo2 + fibo3) = 1 + 2 = 3
fibo5 = (fibo3 + fibo4) = 2 + 3 = 5
如您所见,只要需要多次调用,您都只需访问相应的变量即可获取值,而无需重新计算。
顺便说一下,动态编程并不意味着将递归代码转换为迭代代码。如果需要递归代码,也可以将子结果保存到变量中。在这种情况下,该技术称为记忆。对于我们的示例,它看起来像这样:
// declare and initialize a dictionary
var dict = new Dictionary<int,int>();
for i=1 to n
dict[i] = -1
function F(n) {
if (n < 3)
return 1
else
{
if (dict[n] == -1)
dict[n] = F(n-1) + F(n-2)
return dict[n]
}
}
因此,与分而治之的关系是D&D算法依赖于递归。而且它们的某些版本具有“具有相同参数问题的多功能调用”。在需要DP来改善D&D算法的T(n)的此类示例中,搜索“矩阵链乘法”和“最长公共子序列”。
就目前而言,我可以说动态编程是分而治之范式的扩展。
我不会将它们视为完全不同的东西。因为它们都通过递归将一个问题分解为两个或多个相同或相关类型的子问题来工作,直到它们变得足够简单以至于可以直接解决。然后将子问题的解决方案组合起来,以解决原始问题。
那么为什么我们仍然有不同的范例名称,为什么我将动态编程称为扩展。这是因为仅当问题具有某些限制或先决条件时,才可以将动态编程方法应用于该问题。然后,动态编程扩展了采用记忆或制表技术的分而治之方法。
让我们一步一步走...
正如我们刚刚发现的那样,为了使动态编程适用,必须具有两个必须解决的关键属性:
最优子结构 -可以从子问题的最优解中构造出最优解
子问题重叠 -可以将问题分解为多个子问题,这些子问题可以重复使用几次,或者针对该问题的递归算法可以一遍又一遍地解决相同的子问题,而不是总是生成新的子问题
一旦满足这两个条件,就可以说可以使用动态编程方法解决分而治之的问题。
动态编程方法利用两种技术(记忆和制表法)扩展了分而治之的方法,这两种技术的目的都是存储和重用可能大大提高性能的子问题解决方案。例如,斐波那契函数的朴素递归实现具有时间复杂性,O(2^n)
其中DP解决方案仅需O(n)
时间即可完成。
备注(自上而下的缓存填充)是指缓存和重新使用以前计算的结果的技术。记忆fib
功能因此如下所示:
memFib(n) {
if (mem[n] is undefined)
if (n < 2) result = n
else result = memFib(n-2) + memFib(n-1)
mem[n] = result
return mem[n]
}
制表(自下而上的高速缓存填充)相似,但侧重于填充高速缓存的条目。迭代计算缓存中的值最容易。的列表版本fib
如下所示:
tabFib(n) {
mem[0] = 0
mem[1] = 1
for i = 2...n
mem[i] = mem[i-2] + mem[i-1]
return mem[n]
}
您可以在此处阅读有关记忆和制表比较的更多信息。
您应该在此处掌握的主要思想是,由于我们的分而治之问题具有重叠的子问题,因此可以缓存子问题解决方案,从而使备忘/制表逐步进入现场。
由于我们现在已经熟悉了DP的先决条件及其方法,因此我们准备将上面提到的所有内容合而为一。
如果您想查看代码示例,可以在这里查看更详细的说明,在其中可以找到两个算法示例:二进制搜索和最小编辑距离(Levenshtein距离),它们说明了DP和DC之间的差异。
我假设您已经阅读过Wikipedia和其他学术资源,所以我不会再利用任何这些信息。我还必须告诫我无论如何都不是计算机科学专家,但是我将以我的两分钱分享我对这些主题的理解...
将问题分解为离散的子问题。Fibonacci序列的递归算法是动态规划的一个示例,因为它首先通过求解fib(n-1)来求解fib(n)。为了解决原始问题,它解决了另一个问题。
这些算法通常可以解决类似的问题,然后将它们放到最后。Mergesort是分而治之的经典示例。此示例与Fibonacci示例之间的主要区别在于,在归并排序中,除法(理论上)可以是任意的,并且无论如何拆分,都仍在合并和排序。无论如何拆分数组,都必须完成相同数量的工作才能对数组进行归并排序。解决fib(52)比解决fib(2)需要更多的步骤。
分而治之在每个递归级别都涉及三个步骤:
动态规划包括以下四个步骤:
1. 表征最佳解决方案的结构。
2. 递归定义最优解的值。
3. 计算最佳解决方案的价值。
4. 根据计算出的信息构造最优解。
为了更容易理解,让我们将“分而治之”视为一种蛮力解决方案,并将其优化视为“动态编程”。
只能使用dp优化具有重叠子问题的
NB分治法。
fact(5) = 5* fact(4) = 5 * (4 * fact(3))= 5 * 4 * (3 *fact(2))= 5 * 4 * 3 * 2 * (fact(1))
正如我们在上面看到的,没有事实(x)被重复,因此阶乘具有非重叠问题。
fib(5) = fib(4) + fib(3) = (fib(3)+fib(2)) + (fib(2)+fib(1))
正如我们在上面看到的,fib(4)和fib(3)都使用fib(2)。同样,许多fib(x)被重复。这就是斐波那契有重叠子问题的原因。