Java 中的记忆(1D、2D 和 3D)动态编程

记忆是一种基于动态规划的技术,用于通过保存所提供的结果的记录来确保该方法不会对同一组输入运行多次,从而提高递归算法的性能。inputs(stored in an array)记忆可以是通过实现递归方法的自顶向下方法来实现。

让我们借助基本的斐波那契示例来理解这种情况

一维记忆

我们将考虑一种只有一个非常量参数(只有一个参数改变其值)的递归算法,因此这种方法称为一维记忆。下面的代码是th(all the terms till N)在斐波那契数列中寻找N'。

示例

public int fibonacci(int n) {
   if (n == 0)
      return 0;
   if (n == 1)
      return 1;
   System.out.println("计算斐波那契数: " + n);
   return (fibonacci(n - 1) + fibonacci(n - 2));
}
输出结果

如果我们以 n=5 运行上述代码,它将生成以下输出。

计算斐波那契数: 5
计算斐波那契数: 4
计算斐波那契数: 3
计算斐波那契数: 2
计算斐波那契数: 2
计算斐波那契数: 3
计算斐波那契数: 2

n=5 的斐波那契值:5

值得注意的是,once.Let通过为上述条件i.en=5 的斐波那契数列绘制递归树,计算出的 2 和 3 的斐波那契数比我们更好理解的要多。

这里节点的两个孩子将代表它进行的递归调用。可以看出,F(3) 和 F(2) 被计算了不止一次,并且可以通过在每一步之后缓存结果来避免。

我们将使用实例变量 memoize Set 来缓存result.It首先检查 n 是否已经存在于 memoize Set 中,如果是则返回值,如果不是则计算该值并将其添加到集合中。

示例

import java.util.HashMap;
import java.util.Map;
public class TutorialPoint {
   private Map<Integer, Integer> memoizeSet = new HashMap<>(); //O(1)
   public int fibMemoize(int input) {
      if (input == 0)
         return 0;
      if (input == 1)
         return 1;
      if (this.memoizeSet.containsKey(input)) {
         System.out.println("从计算结果中获取值 " + input);
         return this.memoizeSet.get(input);
      }
      int result = fibMemoize(input - 1) + fibMemoize(input - 2);
      System.out.println("将结果放入缓存中 " + input);
      this.memoizeSet.put(input, result);
      return result;
   }
   public int fibonacci(int n) {
      if (n == 0)
         return 0;
      if (n == 1)
         return 1;
      System.out.println("计算斐波那契数: " + n);
      return (fibonacci(n - 1) + fibonacci(n - 2));
   }
   public static void main(String[] args) {
      TutorialPoint tutorialPoint = new TutorialPoint();
      System.out.println("Fibonacci value for n=5: " + tutorialPoint.fibMemoize(5));
   }
}
输出结果

如果我们运行上面的代码,它将生成以下输出

Adding result in memoizeSet for 2
Adding result in memoizeSet for 3
从计算结果中获取值 2
Adding result in memoizeSet for 4
从计算结果中获取值 3
Adding result in memoizeSet for 5

n=5 的斐波那契值:5

可以看出,2 和 3 的斐波那契数不再计算。在这里,我们引入一个 HashMap 记忆来存储在每个 Fibonacci 计算被检查之前已经计算的值,如果输入的值已经被计算,如果没有,特定输入的值被添加到集合中。

二维记忆

上面的程序我们只有一个非常量参数。在下面的程序中,我们将举一个递归程序的例子,它有两个参数,每次递归调用后都会改变它的值,我们将对两个非常量参数进行记忆以优化它。这称为二维记忆。

例如:-我们将实现标准最长公共子序列(LCS)。如果给定一组序列,最长公共子序列问题是找到所有序列的最大长度的公共子序列。可能的组合将是 2n。

示例

class TP {
   static int computeMax(int a, int b) {
      return (a > b) ? a : b;
   }
   static int longestComSs(String X, String Y, int m, int n) {
      if (m == 0 || n == 0)
         return 0;
      if (X.charAt(m - 1) == Y.charAt(n - 1))
         return 1 + longestComSs(X, Y, m - 1, n - 1);
      else
         return computeMax(longestComSs(X, Y, m, n - 1), longestComSs(X, Y, m - 1, n));
   }
   public static void main(String[] args) {
      String word_1 = "AGGTAB";
      String word_2 = "GXTXAYB";
      System.out.print("LCS 的长度为 " + longestComSs(word_1, word_2, word_1.length(),word_2.length()));
   }
}
输出结果

如果我们运行上面的代码,它将生成以下输出

LCS 的长度为 4

在上面的中途递归树中,lcs("AXY", "AYZ") 求解不止一次。

由于该问题的重叠子结构属性,可以通过使用记忆或制表来避免对相同子问题的预计算。

递归代码的记忆方法实现如下。

示例

import java.io.*;
import java.lang.*;
class testClass {
   final static int maxSize = 1000;
   public static int arr[][] = new int[maxSize][maxSize];
   public static int calculatelcs(String str_1, String str_2, int m, int n) {
      if (m == 0 || n == 0)
         return 0;
      if (arr[m - 1][n - 1] != -1)
         return arr[m - 1][n - 1];
      if (str_1.charAt(m - 1) == str_2.charAt(n - 1)) {
         arr[m - 1][n - 1] = 1 + calculatelcs(str_1, str_2, m - 1, n - 1);
         return arr[m - 1][n - 1];
      }
      else {
         int a = calculatelcs(str_1, str_2, m, n - 1);
         int b = calculatelcs(str_1, str_2, m - 1, n);
         int max = (a > b) ? a : b;
         arr[m - 1][n - 1] = max;
         return arr[m - 1][n - 1];
      }
   }
   public static void main(String[] args) {
      for (int i = 0; i < 1000; i++) {
         for (int j = 0; j < 1000; j++) {
            arr[i][j] = -1;
         }
      }
      String str_1 = "AGGTAB";
      String str_2 = "GXTXAYB";
      System.out.println("LCS 的长度为 " + calculatelcs(str_1, str_2, str_1.length(),str_2.length()));
   }
}
输出结果

如果我们运行上面的代码,它将生成以下输出

LCS 的长度为 4

方法

可以看出,在method(calculatelcs)4 个参数中,有 2 个常量(不影响记忆)和 2 个非常量参数(m 和 n),它们在每个递归方法调用中都会改变其值。因此,为了实现记忆,我们引入了一个二维数组来存储lcs(m,n)arr[m-1][n-1] 的计算值。每当再次调用具有相同参数 m 和 n 的函数时,我们不再执行任何递归调用并返回 arr[m-1][n-1],因为先前的计算lcs(m, n)已经存储在 arr[m- 1][n-1],从而最小化递归调用的数量。

3-D 记忆

这是一种为具有 3 个非常量参数的递归程序实现记忆的方法。这里我们以计算三个字符串的 LCS 长度为例。

这里的方法是为给定的字符串生成所有可能的子序列(总可能的子序列为 3ⁿ),并匹配其中最长的公共子序列。

我们将引入一个 3-D 表来存储计算值。考虑子序列。

  • A1[1...i] i < N

  • A2[1...j] j < M

  • A3[1...k] k < K

如果我们找到一个公共字符(X[i]==Y[j]==Z[k]),我们需要递归剩余的,ones.Or否则我们将计算以下情况的最大值

  • 离开 X[i] 为其他人重复

  • 让 Y[j] 为其他人重复

  • 让 Z[k] 为其他人重复

因此,如果我们将上述想法表述为我们的递归函数,那么

f(N,M,K)={1+f(N-1,M-1,K-1)if (X[N]==Y[M]==Z[K]最大(f(N-1,M,K),f (N,M-1,K),f(N,M,K-1))}

  • f(N-1,M,K) = Leave X[i] recur for others

  • f(N,M-1,K) = 离开 Y[j] 为其他人重复

  • f(N,M,K-1) = 离开 Z[k] 为其他人重复

示例

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
class testClass {
   public static int[][][] arr = new int[100][100][100];
   static int calculatelcs(String str_1, String str_2, String str_3, int m, int n, int o) {
         for (int i = 0; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
               arr[i][j][0] = 0;
            }
         }
         for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= o; j++) {
               arr[0][i][j] = 0;
            }
         }
         for (int i = 0; i <= m; i++) {
            for (int j = 0; j <= o; j++) {
               arr[i][0][j] = 0;
            }
         }
         for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
               for (int k = 1; k <= o; k++) {
                  if (str_1.charAt(i - 1) == str_2.charAt(j-1) && str_2.charAt(j1) == str_3.charAt(k-1)) {
                     arr[i][j][k] = 1 + arr[i - 1][j - 1][k - 1];
                  }
                  else {
                     arr[i][j][k] = calculateMax(arr[i - 1][j][k], arr[i][j - 1][k], arr[i][j][k - 1]);
                  }
               }
            }
         }
         return arr[m][n][o];
   }
   static int calculateMax(int a, int b, int c) {
      if (a > b && a > c)
         return a;
         if (b > c)
         return b;
         return c;
   }
   public static void main(String[] args) {
      String str_1 = "clued";
      String str_2 = "clueless";
      String str_3 = "xcxclueing";
      int m = str_1.length();
      int n = str_2.length();
      int o = str_3.length();
      System.out.print("LCS 的长度为 " + calculatelcs(str_1, str_2, str_3, m, n, o));
   }
}
输出结果

如果我们运行上面的代码,它将生成以下输出

LCS 的长度为 4