📜  动态规划中的重叠子问题性质 | DP-1

📅  最后修改于: 2021-09-22 09:42:59             🧑  作者: Mango

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

在这篇文章中,我们将详细讨论第一个属性(重叠子问题)。动态规划的第二个属性将在下一篇文章中讨论,即 Set 2。
1) 重叠子问题
2) 最优子结构

1) 重叠子问题:
与分而治之,动态规划结合了子问题的解决方案。动态规划主要用于反复需要相同子问题的解决方案时。在动态规划中,子问题的计算解决方案存储在表中,因此不必重新计算这些解决方案。因此,当没有常见(重叠)子问题时,动态规划没有用,因为如果不再需要它们,则存储解决方案没有意义。例如,二分搜索没有常见的子问题。如果我们以斐波那契数的以下递归程序为例,有许多子问题需要一次又一次地解决。

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


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


Javascript


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


Javascript


PHP


执行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

Javascript


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

以下是第 n 个斐波那契数的表格版本。

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

Javascript


PHP


输出:

Fibonacci number is 34

Tabulated 和 Memoized 都存储子问题的解决方案。 Memoized 版是按需填表,Tabulated 版是从第一个条目开始,一个个填满。与 Tabulated 版本不同的是,查找表的所有条目不一定都填写在 Memoized 版本中。例如,LCS 问题的 Memoized 解决方案不一定填充所有条目。

要查看 Memoized 和 Tabulated 解决方案在基本递归解决方案上实现的优化,请参阅以下运行计算第 40 个斐波那契数所花费的时间:
递归解
记忆解决方案
列表解决方案
递归方法花费的时间远远超过上面提到的两种动态规划技术——记忆和制表!

另外,请参阅 Ugly Number 帖子的方法 2,了解一个更简单的示例,其中我们有重叠的子问题并存储子问题的结果。

我们将在以后关于动态规划的帖子中介绍最优子结构属性和更多示例问题。

尝试将以下问题作为这篇文章的练习。
1) 为 LCS 问题编写一个 Memoized 解决方案。请注意,表格解决方案在 CLRS 书中给出。
2)你会如何在记忆和制表之间做出选择?

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程