Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Java

การจำ (1D, 2D และ 3D) การเขียนโปรแกรมแบบไดนามิกใน Java


การท่องจำเป็นเทคนิคที่อิงจากการเขียนโปรแกรมแบบไดนามิกซึ่งใช้เพื่อปรับปรุงประสิทธิภาพของอัลกอริธึมแบบเรียกซ้ำ โดยทำให้แน่ใจว่าเมธอดจะไม่ทำงานสำหรับอินพุตชุดเดียวกันมากกว่าหนึ่งครั้งโดยเก็บบันทึกผลลัพธ์สำหรับอินพุตที่ให้มา (เก็บไว้ใน อาร์เรย์) การท่องจำสามารถทำได้โดยใช้วิธีการแบบเรียกซ้ำจากบนลงล่าง

ให้เราเข้าใจสถานการณ์นี้ด้วยความช่วยเหลือของตัวอย่างฟีโบนักชีพื้นฐาน

การท่องจำ 1 มิติ

เราจะพิจารณาอัลกอริธึมแบบเรียกซ้ำที่มีพารามิเตอร์ไม่คงที่เพียงตัวเดียว (พารามิเตอร์เดียวเท่านั้นที่เปลี่ยนค่าของมัน) ดังนั้นวิธีนี้จึงเรียกว่าการท่องจำ 1 มิติ รหัสต่อไปนี้คือการหา N’th (เงื่อนไขทั้งหมดจนถึง N) ในอนุกรมฟีโบนักชี

ตัวอย่าง

public int fibonacci(int n) {
   if (n == 0)
      return 0;
   if (n == 1)
      return 1;
   System.out.println("Calculating fibonacci number for: " + n);
   return (fibonacci(n - 1) + fibonacci(n - 2));
}

ผลลัพธ์

หากเราเรียกใช้โค้ดด้านบนด้วย n=5 มันจะสร้างผลลัพธ์ต่อไปนี้

Calculating fibonacci number for: 5
Calculating fibonacci number for: 4
Calculating fibonacci number for: 3
Calculating fibonacci number for: 2
Calculating fibonacci number for: 2
Calculating fibonacci number for: 3
Calculating fibonacci number for: 2

ค่าฟีโบนักชีสำหรับ n=5:5

จะสังเกตว่าตัวเลขฟีโบนักชีสำหรับ 2 และ 3 ถูกคำนวณมากกว่าหนึ่งครั้ง ให้เราเข้าใจมากขึ้นโดยการวาดแผนภูมิแบบเรียกซ้ำสำหรับชุดฟีโบนักชีของเงื่อนไขข้างต้น นั่นคือ n=5

ที่นี่ลูกสองคนของโหนดจะเป็นตัวแทนของการเรียกแบบเรียกซ้ำ อย่างที่เห็น F(3) และ F(2) มีการคำนวณมากกว่าหนึ่งครั้งและสามารถหลีกเลี่ยงได้ด้วยการแคชผลลัพธ์หลังจากแต่ละขั้นตอน

เราจะใช้ตัวแปรอินสแตนซ์ memoize Set สำหรับการแคชผลลัพธ์ โดยจะมีการตรวจสอบก่อนว่ามี n อยู่ในชุด memoize หรือไม่ ถ้าใช่ ค่าจะถูกส่งคืน และถ้าไม่ใช่ค่าจะถูกคำนวณและเพิ่มลงในชุด

ตัวอย่าง

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("Getting value from computed result for " + input);
         return this.memoizeSet.get(input);
      }
      int result = fibMemoize(input - 1) + fibMemoize(input - 2);
      System.out.println("Putting result in cache for " + 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("Calculating fibonacci number for: " + 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
Getting value from computed result for 2
Adding result in memoizeSet for 4
Getting value from computed result for 3
Adding result in memoizeSet for 5

ค่าฟีโบนักชีสำหรับ n=5:5

ดังจะเห็นได้ว่าเลข Fibonacci สำหรับ 2 และ 3 จะไม่ถูกคำนวณอีก ในที่นี้ เราขอแนะนำ HashMap ที่จดจำเพื่อเก็บค่าที่คำนวณไว้แล้วก่อนการคำนวณ Fibonacci ทุกครั้งจะได้รับการตรวจสอบในชุดหากสำหรับอินพุต ค่าที่ได้รับคำนวณแล้ว และหากไม่ใช่ค่าสำหรับอินพุตเฉพาะจะถูกเพิ่มเข้าไปในชุด

การท่องจำ 2 มิติ

โปรแกรมข้างต้นเรามีพารามิเตอร์ไม่คงที่เพียงตัวเดียว ในโปรแกรมด้านล่าง เราจะยกตัวอย่างของโปรแกรมแบบเรียกซ้ำที่มีพารามิเตอร์สองตัวซึ่งจะเปลี่ยนค่าของมันหลังจากการเรียกซ้ำทุกครั้ง และเราจะใช้การท่องจำกับพารามิเตอร์ที่ไม่คงที่สองตัวเพื่อปรับให้เหมาะสม สิ่งนี้เรียกว่าการท่องจำ 2 มิติ

ตัวอย่างเช่น:- เราจะใช้ลำดับรองร่วมที่ยาวที่สุดมาตรฐาน(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("Length of LCS is " + longestComSs(word_1, word_2, word_1.length(),word_2.length()));
   }
}

ผลลัพธ์

หากเรารันโค้ดด้านบน มันจะสร้างผลลัพธ์ต่อไปนี้

Length of LCS is 4

การจำ (1D, 2D และ 3D) การเขียนโปรแกรมแบบไดนามิกใน Java

ในแผนผังการเรียกซ้ำครึ่งทางด้านบน lcs("AXY", "AYZ") จะได้รับการแก้ไขมากกว่าหนึ่งครั้ง

เนื่องด้วยคุณสมบัติ Overlapping Substructure ของปัญหานี้ ทำให้สามารถหลีกเลี่ยงการคำนวณล่วงหน้าของปัญหาย่อยเดียวกันได้โดยใช้ Memorization หรือ Tabulation

วิธีการท่องจำของโค้ดแบบเรียกซ้ำมีการใช้งานดังนี้

ตัวอย่าง

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("Length of LCS is " + calculatelcs(str_1, str_2, str_1.length(),str_2.length()));
   }
}

ผลลัพธ์

หากเรารันโค้ดด้านบน มันจะสร้างผลลัพธ์ต่อไปนี้

Length of LCS is 4

แนวทาง

จะเห็นได้ว่าใน method(calculatelcs) มี 4 อาร์กิวเมนต์ที่มีค่าคงที่ 2 ตัว (ซึ่งไม่มีผลกับการท่องจำ) และอาร์กิวเมนต์ที่ไม่คงที่ 2 อาร์กิวเมนต์ (m และ n) ซึ่งเปลี่ยนค่าในการเรียกวิธีการแบบเรียกซ้ำทุกรายการ ดังนั้นเพื่อให้เกิดการท่องจำ เราจึงแนะนำอาร์เรย์ 2 มิติเพื่อเก็บค่าที่คำนวณได้ของ lcs(m,n) ที่ arr[m-1][n-1] เราไม่ทำการเรียกซ้ำอีกต่อไปและส่งคืน arr[m-1][n-1] เมื่อใดก็ตามที่มีการเรียกฟังก์ชันที่มีอาร์กิวเมนต์เดียวกัน m และ n อีกครั้ง เนื่องจากการคำนวณก่อนหน้าของ lcs(m, n) ได้เกิดขึ้นแล้ว เก็บไว้ใน arr[m-1][n-1] ซึ่งช่วยลดจำนวนการโทรซ้ำ

การท่องจำ 3 มิติ

นี่เป็นแนวทางเพื่อให้เกิดการท่องจำสำหรับโปรแกรมแบบเรียกซ้ำซึ่งมีอาร์กิวเมนต์ที่ไม่คงที่ 3 อาร์กิวเมนต์ ต่อไปนี้คือตัวอย่างการคำนวณความยาวของ LCS สำหรับสามสตริง

แนวทางที่นี่คือการสร้างลำดับย่อยที่เป็นไปได้ทั้งหมด (ลำดับย่อยที่เป็นไปได้ทั้งหมดคือ 3ⁿ) สำหรับสตริงที่กำหนดและจับคู่สำหรับลำดับย่อยทั่วไปที่ยาวที่สุดในบรรดาสตริงเหล่านั้น

เราจะมาแนะนำตารางสามมิติเพื่อเก็บค่าที่คำนวณได้ พิจารณาตอนต่อไป

  • A1[1...i] ฉัน

  • A2[1...j] j

  • A3[1...k] k

หากเราพบอักขระทั่วไป (X[i]==Y[j]==Z[k]) เราจำเป็นต้องเรียกอักขระที่เหลือซ้ำ มิฉะนั้น เราจะคำนวณค่าสูงสุดของกรณีต่อไปนี้

  • ปล่อยให้ 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) =ปล่อยให้ X[i] เกิดขึ้นซ้ำสำหรับผู้อื่น

  • 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("Length of LCS is " + calculatelcs(str_1, str_2, str_3, m, n, o));
   }
}

ผลลัพธ์

หากเรารันโค้ดด้านบน มันจะสร้างผลลัพธ์ต่อไปนี้

Length of LCS is 4