📜  动态编程中的子问题重叠属性DP-1

📅  最后修改于: 2021-04-29 13:58:16             🧑  作者: Mango

动态编程是一种算法范式,它通过将给定的复杂问题分解为子问题并存储子问题的结果来避免再次计算相同的结果,从而解决了该问题。以下是问题的两个主要属性,它们表明可以使用动态编程来解决给定的问题。

在这篇文章中,我们将详细讨论第一个属性(子问题重叠)。动态编程的第二个属性将在下一篇文章中讨论,即Set 2。

1)重叠子问题
2)最佳子结构

1)重叠子问题:
与分而治之一样,动态编程将解决方案结合到子问题中。当需要一次又一次地解决相同子问题的解决方案时,主要使用动态编程。在动态编程中,子问题的计算解决方案存储在表中,因此不必重新计算这些问题。因此,当没有常见(重叠)子问题时,动态编程就没有用,因为如果不再需要这些解决方案,那么就没有必要存储解决方案了。例如,二进制搜索没有常见的子问题。如果我们以斐波那契数的递归程序为例,那么会有很多子问题一次又一次地被解决。

/* simple recursive program for Fibonacci numbers */
int fib(int n)
{
   if ( n <= 1 )
      return n;
   return fib(n-1) + fib(n-2);
}

执行fib(5)的递归树

fib(5)
                     /             \
               fib(4)                fib(3)
             /      \                /     \
         fib(3)      fib(2)         fib(2)    fib(1)
        /     \        /    \       /    \
  fib(2)   fib(1)  fib(1) fib(0) fib(1) fib(0)
  /    \
fib(1) fib(0)

我们可以看到函数fib(3)被调用了2次。如果我们将存储fib(3)的值,则可以重新使用旧的存储值,而不必再次进行计算。有以下两种不同的方法来存储值,以便可以重复使用这些值:
a)记忆(自上而下)
b)制表(自下而上)

a)记忆(自上而下):针对问题的记忆程序与递归程序类似,但进行了少量修改,在计算解决方案之前将其查找到查找表中。我们使用所有初始值初始化为NIL的查找数组。每当我们需要子问题的解决方案时,我们都会首先查看查找表。如果存在预先计算的值,则返回该值,否则,我们计算该值并将结果放入查找表中,以便以后可以重用。

以下是第n个斐波纳契数的记忆版本。

C++
/* C++ program for Memoized version
for nth Fibonacci number */
#include 
using namespace std;
#define NIL -1 
#define MAX 100 
  
int lookup[MAX]; 
  
/* Function to initialize NIL 
values in lookup table */
void _initialize() 
{ 
    int i; 
    for (i = 0; i < MAX; i++) 
        lookup[i] = NIL; 
} 
  
/* function for nth Fibonacci number */
int fib(int n) 
{ 
    if (lookup[n] == NIL) 
    { 
        if (n <= 1) 
            lookup[n] = n; 
        else
            lookup[n] = fib(n - 1) + fib(n - 2); 
} 
  
return lookup[n]; 
} 
  
// Driver code
int main () 
{ 
    int n = 40; 
    _initialize(); 
    cout << "Fibonacci number is " << fib(n); 
    return 0; 
} 
  
// This is code is contributed by rathbhupendra


C
/* C program for Memoized version for nth Fibonacci number */
#include
#define NIL -1
#define MAX 100
  
int lookup[MAX];
  
/* Function to initialize NIL values in lookup table */
void _initialize()
{
  int i;
  for (i = 0; i < MAX; i++)
    lookup[i] = NIL;
}
  
/* function for nth Fibonacci number */
int fib(int n)
{
   if (lookup[n] == NIL)
   {
      if (n <= 1)
         lookup[n] = n;
      else
         lookup[n] = fib(n-1) + fib(n-2);
   }
  
   return lookup[n];
}
  
int main ()
{
  int n = 40;
  _initialize();
  printf("Fibonacci number is %d ", fib(n));
  return 0;
}


Java
/* Java program for Memoized version */
public class Fibonacci
{
  final int MAX = 100;
  final int NIL = -1;
  
  int lookup[] = new int[MAX];
  
  /* Function to initialize NIL values in lookup table */
  void _initialize()
  {
    for (int i = 0; i < MAX; i++)
        lookup[i] = NIL;
  }
  
  /* function for nth Fibonacci number */
  int fib(int n)
  {
    if (lookup[n] == NIL)
    {
      if (n <= 1)
          lookup[n] = n;
      else
          lookup[n] = fib(n-1) + fib(n-2);
    }
    return lookup[n];
  }
  
  public static void main(String[] args)
  {
    Fibonacci f = new Fibonacci();
    int n = 40;
    f._initialize();
    System.out.println("Fibonacci number is" + " " + f.fib(n));
  }
  
}
// This Code is Contributed by Saket Kumar


Python
# Python program for Memoized version of nth Fibonacci number
  
# Function to calculate nth Fibonacci number
def fib(n, lookup):
  
    # Base case
    if n == 0 or n == 1 :
        lookup[n] = n
  
    # If the value is not calculated previously then calculate it
    if lookup[n] is None:
        lookup[n] = fib(n-1 , lookup)  + fib(n-2 , lookup) 
  
    # return the value corresponding to that value of n
    return lookup[n]
# end of function
  
# Driver program to test the above function
def main():
    n = 34 
    # Declaration of lookup table
    # Handles till n = 100 
    lookup = [None]*(101)
    print "Fibonacci Number is ", fib(n, lookup)
  
if __name__=="__main__":
    main()
  
# This code is contributed by Nikhil Kumar Singh(nickzuck_007)


C#
// C# program for Memoized versionof nth Fibonacci number 
using System;
  
class GFG
{
      
    static int MAX = 100;
    static int NIL = -1;
    static int []lookup = new int[MAX];
      
    /* Function to initialize NIL 
    values in lookup table */
    static void initialize()
    {
        for (int i = 0; i < MAX; i++)
            lookup[i] = NIL;
    }
      
    /* function for nth Fibonacci number */
    static int fib(int n)
    {
        if (lookup[n] == NIL)
        {
        if (n <= 1)
            lookup[n] = n;
        else
            lookup[n] = fib(n - 1) + fib(n - 2);
        }
        return lookup[n];
    }
      
    // Driver code
    public static void Main()
    {
      
        int n = 40;
        initialize();
        Console.Write("Fibonacci number is" + " " + fib(n));
    }
}
  
// This Code is Contributed by Sam007


C/C++
/* C program for Tabulated version */
#include
int fib(int n)
{
  int f[n+1];
  int i;
  f[0] = 0;   f[1] = 1; 
  for (i = 2; i <= n; i++)
      f[i] = f[i-1] + f[i-2];
  
  return f[n];
}
   
int main ()
{
  int n = 9;
  printf("Fibonacci number is %d ", fib(n));
  return 0;
}


Java
/* Java program for Tabulated version */
public class Fibonacci
{
  int fib(int n)
  {
    int f[] = new int[n+1];
    f[0] = 0;
    f[1] = 1;
    for (int i = 2; i <= n; i++)
          f[i] = f[i-1] + f[i-2];
    return f[n];
  }
  
  public static void main(String[] args)
  {
    Fibonacci f = new Fibonacci();
    int n = 9;
    System.out.println("Fibonacci number is" + " " + f.fib(n));
  }
  
}
// This Code is Contributed by Saket Kumar


Python
# Python program Tabulated (bottom up) version
def fib(n):
  
    # array declaration
    f = [0]*(n+1)
  
    # base case assignment
    f[1] = 1
  
    # calculating the fibonacci and storing the values
    for i in xrange(2 , n+1):
        f[i] = f[i-1] + f[i-2]
    return f[n]
  
# Driver program to test the above function
def main():
    n = 9
    print "Fibonacci number is " , fib(n)
  
if __name__=="__main__":
    main()
  
# This code is contributed by Nikhil Kumar Singh (nickzuck_007)


C#
// C# program for Tabulated version
using System;
  
class GFG
{
    static int fib(int n)
    {
        int []f = new int[n + 1];
        f[0] = 0;
        f[1] = 1;
        for (int i = 2; i <= n; i++)
            f[i] = f[i - 1] + f[i - 2];
        return f[n];
    }
      
    public static void Main()
    {
          
        int n = 9;
        Console.Write("Fibonacci number is" + " " + fib(n));
    }
}
  
// This Code is Contributed by Sam007


PHP


b)制表(自下而上):针对给定问题的制表程序以自下而上的方式构建表并返回表中的最后一个条目。例如,对于相同的斐波那契数,我们首先计算fib(0),然后计算fib(1),然后计算fib(2),再计算fib(3),依此类推。因此,从字面上看,我们正在构建自下而上的子问题解决方案。

以下是第n个斐波纳契数的列表形式。

C / C++

/* C program for Tabulated version */
#include
int fib(int n)
{
  int f[n+1];
  int i;
  f[0] = 0;   f[1] = 1; 
  for (i = 2; i <= n; i++)
      f[i] = f[i-1] + f[i-2];
  
  return f[n];
}
   
int main ()
{
  int n = 9;
  printf("Fibonacci number is %d ", fib(n));
  return 0;
}

Java

/* Java program for Tabulated version */
public class Fibonacci
{
  int fib(int n)
  {
    int f[] = new int[n+1];
    f[0] = 0;
    f[1] = 1;
    for (int i = 2; i <= n; i++)
          f[i] = f[i-1] + f[i-2];
    return f[n];
  }
  
  public static void main(String[] args)
  {
    Fibonacci f = new Fibonacci();
    int n = 9;
    System.out.println("Fibonacci number is" + " " + f.fib(n));
  }
  
}
// This Code is Contributed by Saket Kumar

Python

# Python program Tabulated (bottom up) version
def fib(n):
  
    # array declaration
    f = [0]*(n+1)
  
    # base case assignment
    f[1] = 1
  
    # calculating the fibonacci and storing the values
    for i in xrange(2 , n+1):
        f[i] = f[i-1] + f[i-2]
    return f[n]
  
# Driver program to test the above function
def main():
    n = 9
    print "Fibonacci number is " , fib(n)
  
if __name__=="__main__":
    main()
  
# This code is contributed by Nikhil Kumar Singh (nickzuck_007)

C#

// C# program for Tabulated version
using System;
  
class GFG
{
    static int fib(int n)
    {
        int []f = new int[n + 1];
        f[0] = 0;
        f[1] = 1;
        for (int i = 2; i <= n; i++)
            f[i] = f[i - 1] + f[i - 2];
        return f[n];
    }
      
    public static void Main()
    {
          
        int n = 9;
        Console.Write("Fibonacci number is" + " " + fib(n));
    }
}
  
// This Code is Contributed by Sam007

的PHP


输出:

Fibonacci number is 34

列表化和记忆化都存储子问题的解决方案。在备忘版本中,表是按需填充的,而在制表版本中,从第一个条目开始,所有条目都是一个一个地填充的。与列表格式不同,查找表的所有条目都不一定要填写备注版本。例如,对LCS问题的记忆化解决方案不一定会填写所有条目。

要查看“记忆化”和“列表化”解决方案相对于基本“递归”解决方案所实现的优化,请参阅以下运行计算第40个斐波那契数所花费的时间:

递归解决方案
记忆解决方案
列表解决方案

递归方法所花费的时间远远超过了上面提到的两种动态编程技术–记忆化和制表!

另外,请参见“丑数”文章的方法2,这是一个更简单的示例,其中我们有重叠的子问题,并且我们存储了子问题的结果。

我们将在以后的动态编程文章中讨论最佳子结构属性和更多示例问题。

尝试以下问题作为本帖子的练习。
1)编写LCS问题的备忘解决方案。请注意,表格解决方案在CLRS书中给出。
2)您如何在记忆和制表之间进行选择?

参考:
http://www.youtube.com/watch?v=V5hZoJ6uK-s