分而治之算法与动态规划的区别


139

分而治之算法和动态规划算法有什么区别?这两个术语有何不同?我不了解他们之间的区别。

请举一个简单的例子来说明两者之间的区别,以及它们看起来相似的依据。

Answers:


156

分而治之

分而治之的工作原理是将问题划分为多个子问题,然后递归地解决每个子问题,然后将这些解决方案组合在一起。

动态编程

动态编程是一种用于解决子问题重叠的技术。每个子问题仅解决一次,并且每个子问题的结果存储在表中(通常实现为数组或哈希表),以备将来参考。这些子解决方案可用于获取原始解决方案,并且存储子问题解决方案的技术称为备忘录。

您可能会想到 DP = recursion + re-use

理解差异的经典示例是查看这两种获取第n个斐波那契数的方法。从麻省理工学院检查该材料


分而治之的方法 分而治之的方法

动态规划方法 在此处输入图片说明


9
您是如何制作图像的?使用鼠标?
Vihaan Verma

33
我认为整个答案中最重要的一行是:“子问题重叠”。DP有它,而分而治之却没有
Hasan Iqbal

@HasanIqbalAnik重叠子问题表示反复出现的问题。就像在上面显示的示例中解决fn-2一样。因此在D&C中就存在,这就是为什么它不如DP高效的原因。
Meena Chaudhary

1
奇怪!“重叠子问题”是您在谈论的问题,但“动态编程”是一种算法。我认为区分“问题”和“算法”很重要。

是的,DP会记住重叠部分,从而获得优于“分而治之”的优势。
–imaginerThat

25

分而治之与动态编程之间的另一个区别可能是:

分而治之:

  1. 在子问题上做更多的工作,因此有更多的时间消耗。
  2. 在分而治之中,子问题彼此独立。

动态编程:

  1. 仅解决一次子问题,然后将其存储在表中。
  2. 在动态编程中,子问题不是独立的。

分而治之算法不一定比其DP替代方法做更多的工作。一个例子是用于发现最大算术级数的埃里克森算法。
Michael Foukarakis

17

有时在递归编程时,可以多次调用具有相同参数的函数,这是不必要的。

著名的斐波那契数示例:

           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)的此类示例中,搜索“矩阵链乘法”和“最长公共子序列”。


17

动态编程和分而治之相似

就目前而言,我可以说动态编程是分而治之范式的扩展

我不会将它们视为完全不同的东西。因为它们都通过递归将一个问题分解为两个或多个相同或相关类型的子问题来工作,直到它们变得足够简单以至于可以直接解决。然后将子问题的解决方案组合起来,以解决原始问题。

那么为什么我们仍然有不同的范例名称,为什么我将动态编程称为扩展。这是因为仅当问题具有某些限制或先决条件时,才可以将动态编程方法应用于该问题。然后,动态编程扩展了采用记忆制表技术的分而治之方法。

让我们一步一步走...

动态编程先决条件/限制

正如我们刚刚发现的那样,为了使动态编程适用,必须具有两个必须解决的关键属性:

  • 最优子结构  -可以从子问题的最优解中构造出最优解

  • 子问题重叠  -可以将问题分解为多个子问题,这些子问题可以重复使用几次,或者针对该问题的递归算法可以一遍又一遍地解决相同的子问题,而不是总是生成新的子问题

一旦满足这两个条件,就可以说可以使用动态编程方法解决分而治之的问题。

分而治之的动态编程扩展

动态编程方法利用两种技术(记忆制表法)扩展了分而治之的方法,这两种技术的目的都是存储和重用可能大大提高性能的子问题解决方案。例如,斐波那契函数的朴素递归实现具有时间复杂性,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和DC到底有什么区别

由于我们现在已经熟悉了DP的先决条件及其方法,因此我们准备将上面提到的所有内容合而为一。

动态编程与分而治之

如果您想查看代码示例,可以在这里查看更详细的说明,在其中可以找到两个算法示例:二进制搜索和最小编辑距离(Levenshtein距离),它们说明了DP和DC之间的差异。


1
题外话:您是否使用绘图板来绘制图形?
Geon George

1
@GeonGeorge不,该工程图是用笔制成的,然后进行了扫描
Oleksii Trekhleb,

这是我读到的关于组织DP的最佳答案之一
Ridhwaan Shakeel

8

我假设您已经阅读过Wikipedia和其他学术资源,所以我不会再利用任何这些信息。我还必须告诫我无论如何都不是计算机科学专家,但是我将以我的两分钱分享我对这些主题的理解...

动态编程

将问题分解为离散的子问题。Fibonacci序列的递归算法是动态规划的一个示例,因为它首先通过求解fib(n-1)来求解fib(n)。为了解决原始问题,它解决了另一个问题。

分而治之

这些算法通常可以解决类似的问题,然后将它们放到最后。Mergesort是分而治之的经典示例。此示例与Fibonacci示例之间的主要区别在于,在归并排序中,除法(理论上)可以是任意的,并且无论如何拆分,都仍在合并和排序。无论如何拆分数组,都必须完成相同数量的工作才能对数组进行归并排序。解决fib(52)比解决fib(2)需要更多的步骤


5

我认为这Divide & Conquer是一种递归方法,Dynamic Programming也是表格填充。

例如,Merge Sort是一个Divide & Conquer算法,因为在每个步骤中,您都将数组分成两个部分,递归调用Merge Sort这两个部分,然后合并它们。

Knapsack是一种Dynamic Programming算法,因为您要填写一个表,该表表示整体背包子问题的最佳解决方案。表格中的每个条目都对应于您可以在给定的物品1-j的重量w的袋子中携带的最大值。


1
尽管在很多情况下都是如此,但并非总是将子问题的结果存储在表中。
Gokul

2

分而治之在每个递归级别都涉及三个步骤:

  1. 划分问题划分为若干子。
  2. 征服通过递归解决这些子问题。
  3. 将子问题的解决方案合并为原始问题的解决方案。
    • 这是一种自上而下的方法。
    • 它在子问题上做更多的工作,因此有更多的时间消耗。
    • 例如。斐波那契数列的第n个项可以用O(2 ^ n)时间复杂度来计算。

动态规划包括以下四个步骤:

1. 表征最佳解决方案的结构。
2. 递归定义最优解的值。
3. 计算最佳解决方案的价值。
4. 根据计算出的信息构造最优解。

  • 这是一种自下而上的方法。
  • 与分而治之相比,时间消耗更少,因为我们使用的是较早计算的值,而不是再次计算。
  • 例如。斐波那契数列的第n个项可以用O(n)时间复杂度来计算。

为了更容易理解,让我们将“分而治之”视为一种蛮力解决方案,并将其优化视为“动态编程”。 只能使用dp优化具有重叠子问题的

NB分治法。


分而治之是自下而上,动态编程是自上而下
Bahgat Mashaly

0
  • 分而治之
    • 他们陷入了不重叠的子问题
    • 示例:阶乘数,即fact(n)= n * fact(n-1)
fact(5) = 5* fact(4) = 5 * (4 * fact(3))= 5 * 4 * (3 *fact(2))= 5 * 4 * 3 * 2 * (fact(1))

正如我们在上面看到的,没有事实(x)被重复,因此阶乘具有非重叠问题。

  • 动态编程
    • 他们陷入了重叠的子问题
    • 示例:斐波纳契数,即fib(n)= fib(n-1)+ fib(n-2)
fib(5) = fib(4) + fib(3) = (fib(3)+fib(2)) + (fib(2)+fib(1))

正如我们在上面看到的,fib(4)和fib(3)都使用fib(2)。同样,许多fib(x)被重复。这就是斐波那契有重叠子问题的原因。

  • 由于DP中子问题的重复,我们可以将这些结果保存在表中,从而节省了计算工作量。这称为记忆
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.